[build-system] requires = ["setuptools>=61.0", "tomli_w"] build-backend = "_build_backend" backend-path = ["."] [project] name = "hermes-agent" version = "0.15.0" description = "The self-improving AI agent — creates skills from experience, improves them during use, and runs anywhere" readme = "README.md" requires-python = ">=3.11" authors = [{ name = "Nous Research" }] license = { text = "MIT" } dependencies = [ # Core — every direct dep is exact-pinned to ==X.Y.Z (no ranges). # Rationale: ranges allow PyPI to ship a fresh version of a transitive # at any time without a code review on our side. Exact pins mean the # only way a new package version reaches a user is via an intentional # update on our end (bump the pin in this file, regenerate uv.lock). # This was tightened on 2026-05-12 in response to the Mini Shai-Hulud # worm hitting mistralai 2.4.6 on PyPI; if that release had been # captured by `mistralai>=2.3.0,<3` rather than an exact pin, every # install in the hours before the quarantine would have pulled it. # # When updating: bump the version below AND regenerate uv.lock with # `uv lock` so the transitive resolution stays consistent. Don't # introduce ranges back without a written justification. # # Scope rule: only packages used by EVERY hermes session belong here. # Anything that's provider-specific (anthropic, firecrawl-py, exa-py, # fal-client, edge-tts, parallel-web) belongs in a plugin package # under plugins/ and is installed via extras. Smaller `dependencies` # = smaller blast radius for the next supply-chain attack. "openai==2.24.0", "python-dotenv==1.2.2", "fire==0.7.1", "httpx[socks]==0.28.1", "rich==14.3.3", "tenacity==9.1.4", "pyyaml==6.0.3", "ruamel.yaml==0.18.17", "requests==2.33.0", # CVE-2026-25645 "jinja2==3.1.6", # Bumped from 2.12.5 to 2.13.4 to pull in pydantic-core 2.46.4. # pydantic-core 2.41.5 (pulled by 2.12.5) segfaults when the OpenAI SDK's # Responses API resource is exercised from a non-main thread, which is the # codex_responses dispatch in agent/chat_completion_helpers.py:_call. "pydantic==2.13.4", # Interactive CLI (prompt_toolkit is used directly by cli.py) "prompt_toolkit==3.0.52", # Cron scheduler (built-in feature — scheduled cron/interval jobs use croniter). "croniter==6.0.0", # Skills Hub (GitHub App JWT auth — optional, only needed for bot identity) "PyJWT[crypto]==2.12.1", # CVE-2026-32597 # Windows has no IANA tzdata shipped with the OS, so Python's ``zoneinfo`` # (PEP 615) raises ``ZoneInfoNotFoundError`` for every non-UTC timezone # out of the box. ``tzdata`` ships the Olson database as a data package # Python resolves automatically. No-op on Linux/macOS (which have # /usr/share/zoneinfo). Credits: PR #13182 (@sprmn24). "tzdata==2025.3; sys_platform == 'win32'", # Cross-platform process / PID management. `psutil` is the canonical # answer for "is this PID alive" and process-tree walking across Linux, # macOS and Windows. It replaces POSIX-only idioms like `os.kill(pid, 0)` # (which is a silent killer on Windows — see CONTRIBUTING.md) and # `os.killpg` (which doesn't exist on Windows). "psutil==7.2.2", ] [project.optional-dependencies] # Plugin extras — each references the corresponding workspace member package. # In the source repo, uv resolves these as local workspace packages. # At wheel build time, the custom build backend inlines the actual dep specs # from each plugin's pyproject.toml so the wheel is self-contained on PyPI. anthropic = ["hermes-agent-anthropic"] bedrock = ["hermes-agent-bedrock"] azure-identity = ["hermes-agent-azure"] discord = ["hermes-agent-discord"] exa = ["hermes-agent-exa"] firecrawl = ["hermes-agent-firecrawl"] parallel-web = ["hermes-agent-parallel"] fal = ["hermes-agent-fal"] edge-tts = ["hermes-agent-tts"] tts-premium = ["hermes-agent-tts"] voice = ["hermes-agent-stt"] modal = ["hermes-agent-modal"] daytona = ["hermes-agent-daytona"] hindsight = ["hermes-agent-hindsight"] honcho = ["hermes-agent-honcho"] slack = ["hermes-agent-slack"] telegram = ["hermes-agent-telegram"] matrix = ["hermes-agent-matrix"] dingtalk = ["hermes-agent-dingtalk"] feishu = ["hermes-agent-feishu"] dashboard = ["hermes-agent-dashboard"] # Non-plugin extras (kept as inline dep specs — not workspace members) dev = ["debugpy==1.8.20", "pytest==9.0.2", "pytest-asyncio==1.3.0", "pytest-timeout==2.4.0", "mcp==1.26.0", "ty==0.0.21", "ruff==0.15.10"] cron = [] # croniter is now a core dependency; this extra kept for back-compat # WeCom callback-mode adapter — parses untrusted XML POST bodies from # WeCom-controlled callback endpoints, so we use defusedxml (drop-in # replacement for stdlib xml.etree.ElementTree) to block billion-laughs # and XXE. aiohttp/httpx are already in [messaging]; defusedxml lands # here to keep the dependency local to wecom_callback's threat model. wecom = ["defusedxml==0.7.1"] cli = ["simple-term-menu==1.6.6"] pty = [ "ptyprocess==0.7.0; sys_platform != 'win32'", "pywinpty==2.0.15; sys_platform == 'win32'", ] mcp = ["mcp==1.26.0"] homeassistant = ["aiohttp==3.13.3"] sms = ["aiohttp==3.13.3"] # Computer use — macOS background desktop control via cua-driver (MCP stdio). # The cua-driver binary itself is installed via `hermes tools` post-setup # (curl install script); this extra just pins the MCP client used to talk # to it, which is already provided by the `mcp` extra. computer-use = ["mcp==1.26.0"] acp = ["agent-client-protocol==0.9.0"] # mistral: extra REMOVED 2026-05-12 — `mistralai` PyPI project quarantined # after malicious 2.4.6 release (Mini Shai-Hulud worm). Every version of # `mistralai` returns 404 on PyPI right now, so any pin we'd write is # unresolvable, which breaks `uv lock --check` in CI. # # To restore once PyPI un-quarantines: # 1. Verify the new release is clean (read the changelog, check Socket # advisory page, confirm no malicious code review findings). # 2. Add back: mistral = ["mistralai=="] # 3. Re-enable Mistral in: # - hermes_cli/tools_config.py (un-hide from provider picker) # - hermes_cli/web_server.py (re-add to dashboard STT options) # 4. Run `uv lock` to regenerate transitives. # 5. Optionally re-add to [all] only after a few days of clean operation. google = [ # Required by the google-workspace skill (Gmail, Calendar, Drive, Contacts, # Sheets, Docs). Declared here so packagers (Nix, Homebrew) ship them with # the [all] extra and users don't hit runtime `pip install` paths that fail # in environments without pip (e.g. Nix-managed Python). "google-api-python-client==2.194.0", "google-auth-oauthlib==1.3.1", "google-auth-httplib2==0.3.1", ] youtube = [ # Required by skills/media/youtube-content and # optional-skills/productivity/memento-flashcards (youtube_quiz.py). # Without this declaration uv sync omits the package and both skills fail # at first invocation with ModuleNotFoundError (issue #22243). "youtube-transcript-api==1.2.4", ] termux = [ # Baseline Android / Termux path for reliable fresh installs. "python-telegram-bot[webhooks]==22.6", "hermes-agent[cron]", "hermes-agent[cli]", "hermes-agent[pty]", "hermes-agent[mcp]", "hermes-agent[honcho]", "hermes-agent[acp]", ] termux-all = [ "hermes-agent[termux]", "hermes-agent[google]", "hermes-agent[homeassistant]", "hermes-agent[sms]", "hermes-agent[web]", ] all = [ # All plugin extras + non-plugin extras. # Plugin deps are resolved via workspace members; no inline dep specs here. "hermes-agent[cron]", "hermes-agent[cli]", "hermes-agent[dev]", "hermes-agent[pty]", "hermes-agent[mcp]", "hermes-agent[homeassistant]", "hermes-agent[sms]", "hermes-agent[acp]", "hermes-agent[google]", "hermes-agent[web]", "hermes-agent[youtube]", "hermes-agent[anthropic]", "hermes-agent[bedrock]", "hermes-agent[azure-identity]", "hermes-agent[discord]", "hermes-agent[exa]", "hermes-agent[firecrawl]", "hermes-agent[parallel-web]", "hermes-agent[fal]", "hermes-agent[edge-tts]", "hermes-agent[tts-premium]", "hermes-agent[voice]", "hermes-agent[modal]", "hermes-agent[daytona]", "hermes-agent[hindsight]", "hermes-agent[honcho]", "hermes-agent[slack]", "hermes-agent[telegram]", "hermes-agent[dingtalk]", "hermes-agent[feishu]", "hermes-agent[dashboard]", ] [project.scripts] hermes = "hermes_cli.main:main" hermes-agent = "run_agent:main" hermes-acp = "acp_adapter.entry:main" [tool.setuptools] py-modules = ["run_agent", "model_tools", "toolsets", "batch_runner", "trajectory_compressor", "toolset_distributions", "cli", "hermes_bootstrap", "hermes_constants", "hermes_state", "hermes_time", "hermes_logging", "utils"] [tool.setuptools.package-data] hermes_cli = ["web_dist/**/*", "tui_dist/**/*", "scripts/install.sh", "scripts/install.ps1"] gateway = ["assets/**/*"] plugins = [ "*/dashboard/manifest.json", "*/dashboard/dist/*", "*/dashboard/dist/**/*", ] [tool.setuptools.packages.find] include = ["agent", "agent.*", "tools", "tools.*", "hermes_cli", "gateway", "gateway.*", "tui_gateway", "tui_gateway.*", "cron", "acp_adapter", "plugins", "plugins.*", "providers", "providers.*"] [tool.uv.workspace] members = [ "plugins/model-providers/anthropic", "plugins/model-providers/bedrock", "plugins/model-providers/azure-foundry", "plugins/platforms/telegram", "plugins/platforms/slack", "plugins/platforms/dingtalk", "plugins/platforms/feishu", "plugins/platforms/matrix", "plugins/platforms/discord", "plugins/web/exa", "plugins/web/firecrawl", "plugins/web/parallel", "plugins/memory/honcho", "plugins/memory/hindsight", "plugins/tts", "plugins/stt", "plugins/image_gen/fal_pkg", "plugins/terminals/daytona", "plugins/terminals/modal", "plugins/dashboard", ] [tool.uv.sources] hermes-agent = { workspace = true } hermes-agent-anthropic = { workspace = true } hermes-agent-bedrock = { workspace = true } hermes-agent-azure = { workspace = true } hermes-agent-telegram = { workspace = true } hermes-agent-slack = { workspace = true } hermes-agent-dingtalk = { workspace = true } hermes-agent-feishu = { workspace = true } hermes-agent-matrix = { workspace = true } hermes-agent-discord = { workspace = true } hermes-agent-exa = { workspace = true } hermes-agent-firecrawl = { workspace = true } hermes-agent-parallel = { workspace = true } hermes-agent-honcho = { workspace = true } hermes-agent-hindsight = { workspace = true } hermes-agent-tts = { workspace = true } hermes-agent-stt = { workspace = true } hermes-agent-fal = { workspace = true } hermes-agent-daytona = { workspace = true } hermes-agent-modal = { workspace = true } hermes-agent-dashboard = { workspace = true } [tool.pytest.ini_options] testpaths = ["tests", "plugins"] pythonpath = ["."] markers = [ "integration: marks tests requiring external services (API keys, Modal, etc.)", "real_concurrent_gate: opt out of the autouse stub that disables _detect_concurrent_hermes_instances", ] # pytest-timeout: per-test 30s hard cap with signal method. # This is the fallback inside each per-file pytest subprocess (see # scripts/run_tests_parallel.py). Per-file isolation gives every test # file a fresh Python interpreter; pytest-timeout catches Python-level # hangs within a file. addopts = "-m 'not integration' --timeout=30 --timeout-method=signal" [tool.ty.environment] python-version = "3.13" [tool.ty.rules] unknown-argument = "warn" redundant-cast = "ignore" [tool.ruff] preview = true # required for PLW1514 (unspecified-encoding) — preview rule [tool.ruff.lint] # All other lints are intentionally disabled (see comment history on this # file) while we wrangle typechecks — but PLW1514 is too load-bearing to # keep off. Bare open()/read_text()/write_text() in text mode defaults to # the system locale encoding on Windows (cp1252 on US-locale installs), # which silently corrupts any non-ASCII file content. We had three # separate Windows sandbox regressions in one debug session before # adding the explicit encoding. This rule keeps new code honest. select = ["PLW1514"] [tool.ruff.lint.per-file-ignores] # Tests can intentionally exercise locale-encoding edge cases. "tests/**" = ["PLW1514"] # Skills and plugins are partially user-authored — their own conventions. "skills/**" = ["PLW1514"] "optional-skills/**" = ["PLW1514"] "plugins/**" = ["PLW1514"]