{"repo":"NousResearch/hermes-agent","public_id":"8873","display_label":"Hindsight memory provider missing-dependency handling","summary":"These issues and fixes center on the hindsight-client package being absent, optional, or lost, causing gateway crashes or silent failures. Work covers import guards, early failure, optional dependency declarations, and baking the client into Docker images.","subsystem":"agent","counts":{"members":10,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":4,"merged_prs":2,"attempt_prs":4},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":21818},"resolution":{"state":"partially_resolved","by":"pr-21818,pr-38530"},"freshness":{"state":"verified","last_verified_at":"2026-06-03T23:17:35+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.1875555555555556},"fix_edges":[{"issue_number":8873,"pr_number":21818,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":38128,"pr_number":38530,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":8873,"title":"[Setup]: Hindsight-client required to use hindsight as memory provider, but can't be installed with non-container nixos config","url":null,"state":"closed"},{"kind":"issue","number":18875,"title":"Hindsight memory provider crashes gateway with no error when hindsight-client not installed","url":null,"state":"closed"},{"kind":"issue","number":18876,"title":"Hindsight memory provider crashes gateway when hindsight-client not installed","url":null,"state":"closed"},{"kind":"issue","number":38128,"title":"[Bug]: Docker Hindsight memory provider loses hindsight-client after container recreate/update","url":null,"state":"closed"},{"kind":"pr","number":18882,"title":"fix(hindsight): guard against missing hindsight-client in cloud/external modes","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"6c44471bfdb8a243200abe7aac44371727fda5ca","number":0}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":18924,"title":"fix(memory): guard hindsight_client import to prevent gateway crash when package missing","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"55ac05692","number":0},{"kind":"pr","value":"#18876","number":18876}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":21818,"title":"feat(deps): add hindsight-client as optional dependency","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["pyproject.toml"],"fix_hunks":[{"file":"pyproject.toml","hunks":["@@ -55,6 +55,7 @@ dependencies = [\n modal = [\"modal>=1.0.0,<2\"]\n daytona = [\"daytona>=0.148.0,<1\"]\n vercel = [\"vercel>=0.5.7,<0.6.0\"]\n+hindsight = [\"hindsight-client>=0.4.22\"]\n dev = [\"debugpy>=1.8.0,<2\", \"pytest>=9.0.2,<10\", \"pytest-asyncio>=1.3.0,<2\", \"pytest-xdist>=3.0,<4\", \"pytest-split>=0.9,<1\", \"mcp>=1.2.0,<2\", \"ty>=0.0.1a29,<0.0.22\", \"ruff\"]\n messaging = [\"python-telegram-bot[webhooks]>=22.6,<23\", \"discord.py[voice]>=2.7.1,<3\", \"aiohttp>=3.13.3,<4\", \"slack-bolt>=1.18.0,<2\", \"slack-sdk>=3.27.0,<4\", \"qrcode>=7.0,<8\"]\n cron = []  # croniter is now a core dependency; this extra kept for back-compat"]},{"file":"uv.lock","hunks":["@@ -8,10 +8,6 @@ resolution-markers = [\n     \"python_full_version < '3.12'\",\n ]\n \n-[options]\n-exclude-newer = \"2026-05-01T22:46:56.926194148Z\"\n-exclude-newer-span = \"P7D\"\n-\n [[package]]\n name = \"agent-client-protocol\"\n version = \"0.9.0\"","@@ -1273,6 +1269,15 @@ wheels = [\n     { url = \"https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl\", hash = \"sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7\", size = 5337658, upload-time = \"2026-01-29T23:04:17.404Z\" },\n ]\n \n+[[package]]\n+name = \"defusedxml\"\n+version = \"0.7.1\"\n+source = { registry = \"https://pypi.org/simple\" }\n+sdist = { url = \"https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz\", hash = \"sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69\", size = 75520, upload-time = \"2021-03-08T10:59:26.269Z\" }\n+wheels = [\n+    { url = \"https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl\", hash = \"sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61\", size = 25604, upload-time = \"2021-03-08T10:59:24.45Z\" },\n+]\n+\n [[package]]\n name = \"deprecated\"\n version = \"1.3.1\"","@@ -2014,6 +2019,7 @@ all = [\n     { name = \"ptyprocess\", marker = \"sys_platform != 'win32'\" },\n     { name = \"pytest\" },\n     { name = \"pytest-asyncio\" },\n+    { name = \"pytest-split\" },\n     { name = \"pytest-xdist\" },\n     { name = \"python-telegram-bot\", extra = [\"webhooks\"] },\n     { name = \"pywinpty\", marker = \"sys_platform == 'win32'\" },","@@ -2026,6 +2032,7 @@ all = [\n     { name = \"ty\" },\n     { name = \"uvicorn\", extra = [\"standard\"] },\n     { name = \"vercel\" },\n+    { name = \"youtube-transcript-api\" },\n ]\n bedrock = [\n     { name = \"boto3\" },","@@ -2044,6 +2051,7 @@ dev = [\n     { name = \"mcp\" },\n     { name = \"pytest\" },\n     { name = \"pytest-asyncio\" },\n+    { name = \"pytest-split\" },\n     { name = \"pytest-xdist\" },\n     { name = \"ruff\" },\n     { name = \"ty\" },","@@ -2062,6 +2070,9 @@ google = [\n     { name = \"google-auth-httplib2\" },\n     { name = \"google-auth-oauthlib\" },\n ]\n+hindsight = [\n+    { name = \"hindsight-client\" },\n+]\n homeassistant = [\n     { name = \"aiohttp\" },\n ]","@@ -2162,6 +2173,9 @@ web = [\n yc-bench = [\n     { name = \"yc-bench\", marker = \"python_full_version >= '3.12'\" },\n ]\n+youtube = [\n+    { name = \"youtube-transcript-api\" },\n+]\n \n [package.metadata]\n requires-dist = [","@@ -2234,6 +2248,8 @@ requires-dist = [\n     { name = \"hermes-agent\", extras = [\"voice\"], marker = \"extra == 'all'\" },\n     { name = \"hermes-agent\", extras = [\"web\"], marker = \"extra == 'all'\" },\n     { name = \"hermes-agent\", extras = [\"web\"], marker = \"extra == 'termux-all'\" },\n+    { name = \"hermes-agent\", extras = [\"youtube\"], marker = \"extra == 'all'\" },\n+    { name = \"hindsight-client\", marker = \"extra == 'hindsight'\", specifier = \">=0.4.22\" },\n     { name = \"honcho-ai\", marker = \"extra == 'honcho'\", specifier = \">=2.0.1,<3\" },\n     { name = \"httpx\", extras = [\"socks\"], specifier = \">=0.28.1,<1\" },\n     { name = \"jinja2\", specifier = \">=3.1.5,<4\" },","@@ -2255,6 +2271,7 @@ requires-dist = [\n     { name = \"pyjwt\", extras = [\"crypto\"], specifier = \">=2.12.0,<3\" },\n     { name = \"pytest\", marker = \"extra == 'dev'\", specifier = \">=9.0.2,<10\" },\n     { name = \"pytest-asyncio\", marker = \"extra == 'dev'\", specifier = \">=1.3.0,<2\" },\n+    { name = \"pytest-split\", marker = \"extra == 'dev'\", specifier = \">=0.9,<1\" },\n     { name = \"pytest-xdist\", marker = \"extra == 'dev'\", specifier = \">=3.0,<4\" },\n     { name = \"python-dotenv\", specifier = \">=1.2.1,<2\" },\n     { name = \"python-telegram-bot\", extras = [\"webhooks\"], marker = \"extra == 'messaging'\", specifier = \">=22.6,<23\" },","@@ -2283,8 +2300,9 @@ requires-dist = [\n     { name = \"vercel\", marker = \"extra == 'vercel'\", specifier = \">=0.5.7,<0.6.0\" },\n     { name = \"wandb\", marker = \"extra == 'rl'\", specifier = \">=0.15.0,<1\" },\n     { name = \"yc-bench\", marker = \"python_full_version >= '3.12' and extra == 'yc-bench'\", git = \"https://github.com/collinear-ai/yc-bench.git?rev=bfb0c88062450f46341bd9a5298903fc2e952a5c\" },\n+    { name = \"youtube-transcript-api\", marker = \"extra == 'youtube'\", specifier = \">=1.2.0\" },\n ]\n-provides-extras = [\"modal\", \"daytona\", \"vercel\", \"dev\", \"messaging\", \"cron\", \"slack\", \"matrix\", \"cli\", \"tts-premium\", \"voice\", \"pty\", \"honcho\", \"mcp\", \"homeassistant\", \"sms\", \"computer-use\", \"acp\", \"mistral\", \"bedrock\", \"termux\", \"termux-all\", \"dingtalk\", \"feishu\", \"google\", \"web\", \"rl\", \"yc-bench\", \"all\"]\n+provides-extras = [\"modal\", \"daytona\", \"vercel\", \"hindsight\", \"dev\", \"messaging\", \"cron\", \"slack\", \"matrix\", \"cli\", \"tts-premium\", \"voice\", \"pty\", \"honcho\", \"mcp\", \"homeassistant\", \"sms\", \"computer-use\", \"acp\", \"mistral\", \"bedrock\", \"termux\", \"termux-all\", \"dingtalk\", \"feishu\", \"google\", \"youtube\", \"web\", \"rl\", \"yc-bench\", \"all\"]\n \n [[package]]\n name = \"hf-transfer\"","@@ -2350,6 +2368,23 @@ wheels = [\n     { url = \"https://files.pythonhosted.org/packages/4e/46/1ba8d36f8290a4b98f78898bdce2b0e8fe6d9a59df34a1399eb61a8d877f/hf_xet-1.3.1-cp37-abi3-win_arm64.whl\", hash = \"sha256:851b1be6597a87036fe7258ce7578d5df3c08176283b989c3b165f94125c5097\", size = 3500490, upload-time = \"2026-02-25T00:58:00.667Z\" },\n ]\n \n+[[package]]\n+name = \"hindsight-client\"\n+version = \"0.6.1\"\n+source = { registry = \"https://pypi.org/simple\" }\n+dependencies = [\n+    { name = \"aiohttp\" },\n+    { name = \"aiohttp-retry\" },\n+    { name = \"pydantic\" },\n+    { name = \"python-dateutil\" },\n+    { name = \"typing-extensions\" },\n+    { name = \"urllib3\" },\n+]\n+sdist = { url = \"https://files.pythonhosted.org/packages/33/26/8b8efa4be21fc3ba12ade3b1353d87f9837ec0d3ec2607e8adbf85bc9c63/hindsight_client-0.6.1.tar.gz\", hash = \"sha256:314d0bb9e13622e15586ba1586a799726d405b27bc20d78872474b5d6d96cd51\", size = 99833, upload-time = \"2026-05-08T13:01:23.537Z\" }\n+wheels = [\n+    { url = \"https://files.pythonhosted.org/packages/24/4f/a1d0bc33ef933ecc52e76dc1514163594d25836a5d303c256a61bb61445d/hindsight_client-0.6.1-py3-none-any.whl\", hash = \"sha256:9fdda176ab50f7cec8d7339c6608c148f0cd9ad7e65d9d76192f2db730bc330a\", size = 249379, upload-time = \"2026-05-08T13:01:22.035Z\" },\n+]\n+\n [[package]]\n name = \"honcho-ai\"\n version = \"2.0.1\"","@@ -4439,6 +4474,18 @@ wheels = [\n     { url = \"https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl\", hash = \"sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5\", size = 15075, upload-time = \"2025-11-10T16:07:45.537Z\" },\n ]\n \n+[[package]]\n+name = \"pytest-split\"\n+version = \"0.11.0\"\n+source = { registry = \"https://pypi.org/simple\" }\n+dependencies = [\n+    { name = \"pytest\" },\n+]\n+sdist = { url = \"https://files.pythonhosted.org/packages/2f/16/8af4c5f2ceb3640bb1f78dfdf5c184556b10dfe9369feaaad7ff1c13f329/pytest_split-0.11.0.tar.gz\", hash = \"sha256:8ebdb29cc72cc962e8eb1ec07db1eeb98ab25e215ed8e3216f6b9fc7ce0ec2b5\", size = 13421, upload-time = \"2026-02-03T09:14:31.469Z\" }\n+wheels = [\n+    { url = \"https://files.pythonhosted.org/packages/ae/a1/d4423657caaa8be9b31e491592b49cebdcfd434d3e74512ce71f6ec39905/pytest_split-0.11.0-py3-none-any.whl\", hash = \"sha256:899d7c0f5730da91e2daf283860eb73b503259cb416851a65599368849c7f382\", size = 11911, upload-time = \"2026-02-03T09:14:33.708Z\" },\n+]\n+\n [[package]]\n name = \"pytest-xdist\"\n version = \"3.8.0\"","@@ -6165,6 +6212,19 @@ dependencies = [\n     { name = \"typer\", marker = \"python_full_version >= '3.12'\" },\n ]\n \n+[[package]]\n+name = \"youtube-transcript-api\"\n+version = \"1.2.4\"\n+source = { registry = \"https://pypi.org/simple\" }\n+dependencies = [\n+    { name = \"defusedxml\" },\n+    { name = \"requests\" },\n+]\n+sdist = { url = \"https://files.pythonhosted.org/packages/60/43/4104185a2eaa839daa693b30e15c37e7e58795e8e09ec414f22b3db54bec/youtube_transcript_api-1.2.4.tar.gz\", hash = \"sha256:b72d0e96a335df599d67cee51d49e143cff4f45b84bcafc202ff51291603ddcd\", size = 469839, upload-time = \"2026-01-29T09:09:17.088Z\" }\n+wheels = [\n+    { url = \"https://files.pythonhosted.org/packages/be/95/129ea37efd6cd6ed00f62baae6543345c677810b8a3bf0026756e1d3cf3c/youtube_transcript_api-1.2.4-py3-none-any.whl\", hash = \"sha256:03878759356da5caf5edac77431780b91448fb3d8c21d4496015bdc8a7bc43ff\", size = 485227, upload-time = \"2026-01-29T09:09:15.427Z\" },\n+]\n+\n [[package]]\n name = \"zipp\"\n version = \"3.23.0\"\n"]}]},{"kind":"pr","number":22586,"title":"fix(memory): probe hindsight_client import in is_available (#18875)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#18882","number":18882},{"kind":"pr","value":"#18924","number":18924},{"kind":"pr","value":"#18875","number":18875}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":32976,"title":"fix(hindsight): guard missing cloud client dependency","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"6c44471bfdb8a243200abe7aac44371727fda5ca","number":0}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":38530,"title":"fix(docker): bake hindsight-client into the image (#38128)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["Dockerfile","tests/tools/test_dockerfile_pid1_reaping.py"],"fix_hunks":[{"file":"Dockerfile","hunks":["@@ -157,10 +157,17 @@ RUN npm install --prefer-offline --no-audit && \\\n # so Docker users can use these providers without requiring runtime\n # lazy-install access to PyPI (often blocked in containerized envs).\n #\n+# The hindsight memory provider's client (hindsight-client) is baked in\n+# for the same reason: it lazy-installs into /opt/hermes/.venv at first\n+# use, which lives inside the (immutable) image layer rather than the\n+# mounted /opt/data volume, so it is lost on every container recreate /\n+# image update and recall/retain then fails with\n+# `ModuleNotFoundError: No module named 'hindsight_client'` (#38128).\n+#\n # The editable link is created after the source copy below.\n COPY pyproject.toml uv.lock ./\n RUN touch ./README.md\n-RUN uv sync --frozen --no-install-project --extra all --extra messaging --extra anthropic --extra bedrock --extra azure-identity\n+RUN uv sync --frozen --no-install-project --extra all --extra messaging --extra anthropic --extra bedrock --extra azure-identity --extra hindsight\n \n # ---------- Source code ----------\n # .dockerignore excludes node_modules, so the installs above survive."]},{"file":"tests/tools/test_dockerfile_pid1_reaping.py","hunks":["@@ -172,6 +172,23 @@ def test_dockerfile_preinstalls_gateway_messaging_dependencies(dockerfile_text):\n     )\n \n \n+def test_dockerfile_preinstalls_hindsight_memory_dependency(dockerfile_text):\n+    sync_steps = [\n+        step for step in _run_steps(dockerfile_text)\n+        if \"uv sync\" in step and \"--no-install-project\" in step\n+    ]\n+\n+    assert sync_steps, \"Dockerfile must install Python dependencies with uv sync\"\n+    assert any(\"--extra hindsight\" in step for step in sync_steps), (\n+        \"Published Docker images must preload the [hindsight] extra so the \"\n+        \"native Hindsight memory provider's client (hindsight-client) is baked \"\n+        \"into /opt/hermes/.venv. It lazy-installs into the image layer (not the \"\n+        \"mounted /opt/data volume), so without baking it in recall/retain fails \"\n+        \"with `ModuleNotFoundError: No module named 'hindsight_client'` after \"\n+        \"every container recreate / image update (#38128).\"\n+    )\n+\n+\n def test_dockerfile_builds_tui_assets(dockerfile_text):\n     assert any(\n         \"ui-tui\" in step and \"npm\" in step and \"run build\" in step\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}