[build-system] build-backend = "hatchling.build" requires = [ "hatch-vcs", "hatchling" ] [project] name = "mindroom" description = "A universal interface for AI agents with persistent memory, where every conversation has a home" readme = "README.md" license = "Apache-2.0" requires-python = ">=3.13,<3.15" classifiers = [ "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", ] dynamic = [ "version" ] dependencies = [ "agno==3.0.9", "aiohttp>=3.13.3", # Claude SDK 2 moves requests to API version 2026-10-21, a migration of its own. "anthropic>=1.17,<2", "anyio>=4.10", "authlib>=1.6", "certifi>=2026.4.22", "cron-descriptor>=1.4.5", "croniter>=6", "cryptography>=47", # Tool descriptions and parameter descriptions come from docstrings (tool_system/tool_schema.py). "docstring-parser>=0.18", "fastapi>=0.136.3", "google-auth>=2.40.3", "google-genai>=2", "h11>=0.16", "httpx>=0.27", # 2.15.1 stops a long-lived client's TLS handshakes from growing slower on Linux. "httpx2>=2.15.1", "humanize>=4.12.3", "idna>=3.13", "jinja2>=3.1", "json5>=0.13", "jsonschema>=4.26", "markdown-it-py>=4", "mcp[cli]>=2.3,<3", "mdit-py-plugins>=0.5", # Owned to-device transport requires the durable NIO API. "mindroom-nio[e2e]==1.3", "ollama>=0.6.1", # 3.30 returns a streamed Chat Completions connection to the pool. "openai>=3.30", "packaging>=24", "pillow>=10.2", "pydantic>=2.12.5", "pydantic-settings>=2.10.1", "pygments>=2.20", "pyjwt>=2.13", # Request sizing reads a PDF attachment's page count (token_counting/requests.py). "pypdf>=6.21.4", "python-dotenv>=1", "python-multipart>=0.0.27", "pyyaml>=6", "questionary>=2.1.1", "referencing>=0.37", "rich", "segno>=1.6.6", "starlette>=1.3.1", "structlog>=24.1", "tiktoken>=0.12", "typer>=0.24", "uvicorn>=0.35", "watchfiles>=1", "websockets>=14", ] [[project.authors]] name = "mindroom team" [project.optional-dependencies] agent_manager = [] agentql = [ "agentql", "playwright", ] airflow = [] apify = [ "apify-client>=1.12.2; platform_machine!='aarch64'", ] approved_egress = [] arxiv = [ "arxiv", ] atlassian = [] attachments = [] aws_lambda = [ "boto3>=1.40.8", ] aws_ses = [ "boto3>=1.40.8", ] baidusearch = [ "baidusearch", "pycountry", ] aws_bedrock = [ "boto3>=1.40.8", ] bitbucket = [ "requests", ] brandfetch = [ "httpx>=0.27", ] brightdata = [ "requests", ] browser_mcp = [] browser = [ "playwright", ] browserbase = [ "browserbase", "playwright", ] cal_com = [ "pytz", "requests", ] calculator = [] callback_manager = [] cartesia = [ "cartesia", ] chat_ui = [] chromadb = [ "chromadb>=1.5.8", ] claude_agent = [ "claude-agent-sdk>=0.2.140", ] clickup = [ "requests", ] coding = [] compact_context = [] composio = [ "composio-agno", ] config_manager = [ "pydantic>=2", "pyyaml>=6", ] confluence = [ "atlassian-python-api", ] contacts = [] crawl4ai = [ "crawl4ai>=0.7.3", ] csv = [ "duckdb", ] custom_api = [ "requests", ] daytona = [ "daytona", ] delegate = [] discord = [ "requests", ] docker = [ "docker", ] duckdb = [ "duckdb", ] duckduckgo = [ "ddgs", ] dynamic_tools = [] dynamic_workflow = [] report_publishing = [] e2b = [ "e2b-code-interpreter", ] egress_credentials = [] eleven_labs = [ "elevenlabs", ] email = [] exa = [ "exa-py", ] external_trigger_manager = [] fal = [ "fal-client", ] file = [] file_generation = [ "python-docx>=1.2", "reportlab", ] financial_datasets_api = [ "requests", ] firecrawl = [ "firecrawl-py>=3", ] gemini = [ "google-genai", ] giphy = [ "httpx>=0.27", ] github = [ # PyGithub 2.9 injects URL metadata into dictionary responses, breaking Agno's numeric language parser. "pygithub>=2.5,<2.9", ] gmail = [ "google-api-python-client>=2.178", "google-auth>=2.40.3", "google-auth-httplib2>=0.2", "google-auth-oauthlib>=1.2.2", ] google_bigquery = [ "google-api-python-client>=2.178", "google-auth>=2.40.3", "google-auth-httplib2>=0.2", "google-cloud-bigquery>=3.41", ] google_calendar = [ "google-api-python-client>=2.178", "google-auth>=2.40.3", "google-auth-httplib2>=0.2", "google-auth-oauthlib>=1.2.2", ] google_drive = [ "google-api-python-client>=2.178", "google-auth>=2.40.3", "google-auth-httplib2>=0.2", "google-auth-oauthlib>=1.2.2", ] google_docs = [ "google-api-python-client>=2.178", "google-auth>=2.40.3", "google-auth-httplib2>=0.2", ] google_maps = [ "google-maps-places", "googlemaps", ] google_scholar = [ "scholarly", ] google_sheets = [ "google-api-python-client>=2.178", "google-auth-httplib2>=0.2", "google-auth-oauthlib>=1.2.2", ] google_tasks = [ "google-api-python-client>=2.178", "google-auth>=2.40.3", "google-auth-httplib2>=0.2", ] googlesearch = [ "ddgs", ] groq = [ "groq>=0.31", ] hackernews = [ "httpx>=0.27", ] homeassistant = [ "httpx>=0.27", ] invite_router = [] jina = [ "httpx>=0.27", "pydantic>=2", ] jira = [ "jira>=3.10.5", ] knowledge_graph = [ "graphiti-core[falkordblite]==0.31.1", # C parser for FalkorDB replies; redis-py parses large ones in pure Python on the event loop otherwise. "hiredis", "psutil", ] kubernetes = [ "kubernetes>=28.1", ] linear = [ "requests", ] linkup = [ "linkup-sdk>=0.2.8", ] lumalabs = [ "lumaai", ] matrix_api = [] matrix_calls = [ # 1.8.9 runs on OpenAI SDK 3 and pins the livekit 1.1.21 audio mixer the call tests cover. "livekit-agents[openai]>=1.8.9", ] matrix-e2ee = [] matrix_message = [] matrix_room = [] matrix_voice_message = [] mem0 = [ "mem0ai>=2.2.1", ] memory = [] microsoft_files = [] microsoft_meetings = [] microsoft_onenote = [] microsoft_people = [] microsoft_sharepoint = [] microsoft_teams = [] microsoft_todo = [] modelslabs = [ "requests", ] moviepy_video_tools = [ "moviepy", ] neo4j = [ "neo4j", ] newspaper = [ "lxml-html-clean", "newspaper4k", ] notion = [ "notion-client", ] numpy = [ "numpy>=2", ] oauth_connections = [] openai = [ "openai", ] openbb = [ "openbb", "polars", "pyarrow", ] openclaw_compat = [] openweather = [ "requests", ] outlook_calendar = [] outlook_mail = [] oxylabs = [ "oxylabs", ] pandas = [ "pandas>=2.2", ] postgres = [ "psycopg[binary]", ] pubmed = [ "httpx>=0.27", ] python = [] react = [] reasoning = [] reddit = [ "praw", ] redshift = [ "redshift-connector", ] replicate = [ "replicate>=1.0.7", ] resend = [ "resend", ] scheduler = [] script = [] scrapegraph = [ "scrapegraph-py>=2.1", ] searxng = [] self_config = [ "pydantic>=2", "pyyaml>=6", ] sentence-transformers = [ "sentence-transformers>=6.1", "torchvision", "transformers>=5.19", ] serpapi = [ "google-search-results>=2.4.2", ] serper = [ "requests", ] shared_directories = [] shell = [] shopify = [ "httpx>=0.27", ] skill_manage = [] slack = [] sleep = [] spider = [ "spider-client", ] spotify = [ "httpx>=0.27", ] sql = [ "sqlalchemy", ] stop_reply = [] supabase = [ "supabase>=2.18.1", ] tavily = [ "tavily-python", ] telegram = [ "pytelegrambotapi>=4.32", ] terminal = [] thread_model = [] thread_move = [] thread_resolution = [] thread_summary = [] thread_tags = [] todo = [] todoist = [ "todoist-api-python", ] trafilatura = [ "trafilatura", ] trello = [ "py-trello", ] twilio = [ "twilio", ] unsplash = [] update_awareness = [] usage_stats = [] visualization = [ "matplotlib", ] web_browser_tools = [] webex = [ "webexpythonsdk", ] website = [ "beautifulsoup4", "httpx>=0.27", ] whatsapp = [ "httpx>=0.27", ] wikipedia = [ "wikipedia", ] x = [ "tweepy", ] yfinance = [ "yfinance", ] youtube = [ "youtube-transcript-api", ] zendesk = [ "requests", ] zep = [ "zep-cloud>=3.3", ] zoom = [ "requests", ] desktop = [ "pillow>=10.2", "pyautogui>=0.9.54", "pyobjc-framework-applicationservices>=12.2.1; sys_platform=='darwin'", "pyobjc-framework-cocoa>=12.2.1; sys_platform=='darwin'", "pyobjc-framework-screencapturekit>=12.2.1; sys_platform=='darwin'", ] [project.scripts] mindroom-agent = "mindroom.agent_cli.main:main" mindroom = "mindroom.cli.main:main" [project.urls] documentation = "https://github.com/mindroom-ai/mindroom" homepage = "https://github.com/mindroom-ai/mindroom" repository = "https://github.com/mindroom-ai/mindroom" [dependency-groups] dev = [ "aioresponses>=0.7.8", "hypothesis>=6.161.1", "markdown>=3.10.2", "markdown-code-runner>=2.4", "markdown-gfm-admonition", "pre-commit>=4.2", "pre-commit-uv>=4.1.4", "privata>=0.8", "pytest>=8.4.1", "pytest-asyncio>=1.1", "pytest-cov>=6.2.1", "pytest-mock>=3.14.1", "pytest-shm>=0.1.3", "pytest-timeout>=2.4", "pytest-xdist>=3.8", "requests>=2.32.4", "ruff>=0.12.5", "tach>=0.34.1", "ty>=0.0.17", "vulture>=2.16", "zensical", ] # Only the frozen helper's dependencies; the backend's ML packages are not needed. # uv resolves this group with the application's exact NIO pin in the same lockfile. desktop-helper = [ "certifi", "httpx>=0.27", "mcp[cli]>=2.3,<3", "mindroom-nio[e2e]", "pillow>=10.2", "pyautogui>=0.9.54", "pyinstaller==6.16", "pyobjc-framework-applicationservices>=12.2.1; sys_platform=='darwin'", "pyobjc-framework-cocoa>=12.2.1; sys_platform=='darwin'", "pyobjc-framework-screencapturekit>=12.2.1; sys_platform=='darwin'", "python-dotenv>=1.1.1", "pyyaml>=6.0.2", "structlog>=25.4", ] [tool.hatch.build.targets.wheel] packages = [ "src/mindroom" ] [tool.hatch.build.targets.wheel.force-include] skills = "mindroom/_bundled_skills" [tool.hatch.build.targets.wheel.hooks.custom] [tool.hatch.version] source = "vcs" [tool.hatch.version.raw-options] version_scheme = "no-guess-dev" [tool.uv] override-dependencies = [ # crawl4ai 0.8.6 declares lxml~=5.3, which caps lxml below 6. # The locked lxml 5.4.0 has no cp314 Linux wheels. # On Python 3.14 it source-builds against libxml2/libxslt, so use lxml 6.1+ there. "lxml>=5.3,<6; python_version < '3.14'", "lxml>=6.1.0; python_version >= '3.14'", # composio-core 0.7.21 declares Pillow<11,>=10.2.0. # The locked Pillow 10.4.0 has no cp314 Linux wheels. # On Python 3.14 it source-builds against zlib, so use Pillow 11.x there. "pillow>=10.2,<11; python_version < '3.14'", "pillow>=11,<12; python_version >= '3.14'", ] # Exact version pins for everything uv installs into isolated build environments, so release, desktop-helper, # and image builds install only the build package versions this revision names. # uv checks no file hashes here, so a file later added to a pinned release on PyPI can still be installed. # uv build, uv sync (through uv.lock), and uv pip run with --project all read these. # hatchling, hatch-vcs, and their requirements build this project; editable installs add editables. # Locked dependencies that publish only sdists build with setuptools; pyscreeze adds wheel, # and googlemaps adds requests and its requirements. build-constraint-dependencies = [ "certifi==2026.7.22", "charset-normalizer==3.5.2", "editables==0.6", "hatch-vcs==0.5.0", "hatchling==1.32.4", "idna==3.20", "packaging==26.3", "pathspec==1.1.1", "pluggy==1.6.0", "requests==2.34.2", "setuptools-scm==10.3.4", "setuptools==84.0.0", "tomlkit==0.15.1", "trove-classifiers==2026.9.21.13", "urllib3==2.8.0", "vcs-versioning==2.5.0", "wheel==0.48.0", ] [tool.ruff] target-version = "py313" line-length = 120 [tool.ruff.lint] select = [ "ALL", ] ignore = [ "ASYNC240", # Async pathlib guidance is too broad for this asyncio codebase/tests "ASYNC250", # Blocking input in async manual tests is intentional "B905", # `zip()` without an explicit `strict=` parameter "BLE001", # Do not catch blind exception "D203", # Incompatible with D211; prefer no blank line before class docstrings "D107", # Missing docstring in `__init__` "D213", # Incompatible with D212; prefer multi-line summaries on the first line "D401", # First line of docstring should be in imperative mood "D402", # First line should not be the function's signature "E501", # Line too long "FBT001", # Boolean-typed positional argument in function definition "FBT002", # Boolean default value "FBT003", # Boolean positional value in call "FIX002", # Line contains TODO "G004", # Logging statement uses f-string "PLR0913", # Too many arguments to function call (N > 5) "PLR2004", # Magic value used "PLW0108", # Lambda warnings from dependency injection and test doubles are not actionable here "PLW0603", # Using the global statement to update `X` is discouraged "PYI034", # `Self` guidance is not useful in concrete test doubles "S101", # Use of assert detected "S603", # `subprocess` call: check for execution of untrusted input "S607", # Starting a process with a partial executable path "SLF001", # Private member accessed "T20", # flake8-print "TD002", # Missing author in TODO; try: `# TODO(): ...` "TD003", # Missing issue link on the line following this TODO "UP042", # `StrEnum` migration is outside the scope of hook maintenance "UP047", # PEP 695 type-parameter migration is outside the scope of hook maintenance ] per-file-ignores.".claude/skills/*/scripts/*" = [ "INP001", # Part of an implicit namespace package (standalone skill scripts) ] per-file-ignores.".github/scripts/*" = [ "INP001", # Part of an implicit namespace package (standalone scripts) ] per-file-ignores."cluster/k8s/kind/*" = [ "INP001", # Part of an implicit namespace package (namespace package for python -m invocation) ] per-file-ignores."tests/fixtures/*/*.py" = [ "INP001", # Example plugins under test fixtures load by path, never as packages ] per-file-ignores."src/mindroom/tools/*" = [ "PLC0415", # `import` should be at the top-level of a file" ] per-file-ignores."src/mindroom/tools/__init__.py" = [ "F401", # the package imports each registration only so it registers its tool ] [tool.vulture] paths = [ "src/mindroom", "vulture_whitelist.py" ] ignore_decorators = [ "@register_tool_with_metadata" ] min_confidence = 60 sort_by_size = true [tool.ty.environment] python-version = "3.13" # Mock patterns cause many false positives in tests (e.g., mock.call_args # returns _Call | None, attribute access on MagicMock, passing mocks as typed # arguments). [[tool.ty.overrides]] include = [ "tests/" ] rules = { call-non-callable = "ignore", invalid-argument-type = "ignore", invalid-assignment = "ignore", invalid-return-type = "ignore", no-matching-overload = "ignore", not-iterable = "ignore", not-subscriptable = "ignore", too-many-positional-arguments = "ignore", unresolved-attribute = "ignore", unsupported-operator = "ignore" } # The macOS accessibility backend imports pyobjc, declared # `sys_platform=='darwin'` and so genuinely absent everywhere else. Checking on # another platform cannot resolve it and should not pretend to; the alternative # is declaring the whole project macOS-targeted, which would hide real errors in # the paths that are not. The imports are already function-level. # # `unused-ignore-comment` goes with it: the per-attribute ignores in those files # are load-bearing on macOS, where pyobjc resolves and ty sees real types, and # necessarily unused wherever the import does not resolve. One file cannot # satisfy both platforms, so the warning is scoped off rather than the # directives deleted. [[tool.ty.overrides]] include = [ "src/mindroom/desktop/" ] rules = { unresolved-import = "ignore", unused-ignore-comment = "ignore" } [tool.ty.rules] deprecated = "ignore" # FastAPI on_event deprecation; refactor to lifespan separately [tool.ty.src] include = [ "src", "tests" ] [tool.ty.terminal] error-on-warning = true [tool.pytest.ini_options] testpaths = [ "tests" ] python_files = "test_*.py" asyncio_mode = "auto" pythonpath = [ "src" ] addopts = """ -p no:tach -q -n auto --dist worksteal --asyncio-mode=strict --timeout 60 --durations 20 """ markers = [ "e2e: marks end-to-end tests", "slow: marks tests as slow", ] # A full run peaks at about 3 GiB of temporary files on /dev/shm, mostly worker virtualenvs. shm_min_free_gib = 4 [tool.coverage.report] exclude_lines = [ "pragma: no cover", "raise NotImplementedError", "if TYPE_CHECKING", "if __name__ == .__main__.:", ] exclude_also = [ '^\s*\.\.\.$' ]