docs(troubleshooting): bake the #1695 diagnostic flow into the error message + a new troubleshooting doc

Closes #1695.

@Patrick-81 reported the bare "AIAgent not available -- check that
hermes-agent is on sys.path" error on a symlinked install (~/Programmes/hermes-agent
linked to ~/hermes-agent). The maintainer's response — three diagnostic
commands plus `pip install -e .` in the agent dir — fixed it for them.
This PR captures both halves of that learning so the next user with the
same shape doesn't have to file a new issue:

1. **Error message diagnostic block.** New helper
   `_aiagent_import_error_detail()` in api/streaming.py builds a multi-line
   diagnostic when the import fails, including:
     - the running Python interpreter
     - HERMES_WEBUI_AGENT_DIR (set value, or "(not set)")
     - sys.path entries that mention hermes/agent (or "no entries mention..."
       — itself a strong diagnostic signal)
     - the most-common fix (`pip install -e .` in the agent dir)
     - a pointer to docs/troubleshooting.md

   The original error message string is preserved as the FIRST line so
   existing log scrapers and docs-search keep matching.

   Helper is kept as a separate function so it stays out of the hot path
   until we actually need to raise — building it on every successful import
   would be wasted work.

2. **New docs/troubleshooting.md.** Symptom → Why → Diagnostic commands →
   Fix → When-to-file-a-bug template, with one entry to start: the
   "AIAgent not available" flow Patrick-81 walked through. Future
   recurring failure modes follow the same template. Required a one-line
   addition to .gitignore — docs/* is gitignored with an allowlist, and
   the new file needed `!docs/troubleshooting.md` to be tracked.

3. **README link.** docs/troubleshooting.md added to the `## Docs` section
   so users know where to look first.

13 regression tests in tests/test_1695_aiagent_import_error_detail.py:
9 for the helper output shape (preserves original message line, includes
running python, shows HERMES_WEBUI_AGENT_DIR set/unset both ways, includes
pip-install-e hint, points at troubleshooting doc, lists relevant sys.path
entries when present, says "no entries..." when absent, output is multi-line)
plus 4 for the docs-presence regression (file exists, has the AIAgent
section, includes pip install -e ., describes the diagnostic chain with
readlink + agent/__init__.py verification).

190 streaming/aiagent tests pass after the change. ast.parse on
api/streaming.py clean.

CI failure on prior push was due to the docs/* gitignore swallowing the
new troubleshooting.md file silently — this commit adds the allowlist
entry so the file is tracked.
This commit is contained in:
nesquena-hermes
2026-05-05 22:01:23 +00:00
parent a6e2bbb263
commit 29878259ca
8 changed files with 345 additions and 3 deletions

1
.gitignore vendored
View File

@@ -42,6 +42,7 @@ docs/*
!docs/ui-ux/**
!docs/docker.md
!docs/supervisor.md
!docs/troubleshooting.md
# Local-only PR review harness: rendering drivers, sample bank, fixtures.
# Used by Claude during deep reviews; never shared in the repo.

View File

@@ -1,5 +1,15 @@
# Hermes Web UI -- Changelog
## [v0.51.7] — 2026-05-05 — single-PR docs+dx (#1695)
### Changed
- **#1695 — better diagnostic on `AIAgent not available` (DX + docs).** When the WebUI was launched with a Python that can't import `run_agent.AIAgent`, every chat request raised a bare `ImportError("AIAgent not available -- check that hermes-agent is on sys.path")` with no information about which Python was running, where it was looking, or what to do next. @Patrick-81 reported the symptom on a symlinked install (#1695); the maintainer's response (which Patrick confirmed worked) was a three-step diagnostic flow that we've now baked into the error message itself plus a new `docs/troubleshooting.md`. The error now includes: the running Python interpreter, the `HERMES_WEBUI_AGENT_DIR` env (set vs not set), the relevant `sys.path` entries (those mentioning hermes/agent), the most-common fix (`pip install -e .` in the agent dir), and a pointer to `docs/troubleshooting.md`. Docs entry walks through `ls`/`readlink`/`pip install -e .` diagnostic steps, three common failure modes (not on sys.path, broken symlink, wrong override), and when to file a bug.
### Added
- **`docs/troubleshooting.md`** — new diagnostic-flow doc with one entry to start (`AIAgent not available`); structured as Symptom → Why → Diagnostic commands → Fix → When to file a bug. Linked from README's `## Docs` section. Future failure-mode entries follow the same template.
## [v0.51.6] — 2026-05-05 — 5-PR full-sweep batch
### Added

View File

@@ -535,6 +535,7 @@ State lives outside the repo at `~/.hermes/webui-mvp/` by default
- `CHANGELOG.md` -- release notes per sprint
- `SPRINTS.md` -- forward sprint plan with CLI + Claude parity targets
- `THEMES.md` -- theme system documentation, custom theme guide
- `docs/troubleshooting.md` -- diagnostic flows for common failures (e.g. "AIAgent not available")
## Contributors

View File

@@ -2,7 +2,7 @@
> Web companion to the Hermes Agent CLI. Same workflows, browser-native.
>
> Last updated: v0.51.6 (May 5, 2026) — 4537 tests collected
> Last updated: v0.51.7 (May 5, 2026) — 4550 tests collected — single-PR docs+dx (#1695: AIAgent not available diagnostic)
> Test source: `pytest tests/ --collect-only -q`
> Per-version detail: see [CHANGELOG.md](./CHANGELOG.md)

View File

@@ -1835,7 +1835,7 @@ Bridged CLI sessions:
---
*Last updated: v0.51.6, May 5, 2026*
*Last updated: v0.51.7, May 5, 2026*
*Total automated tests collected: 4537*
*Regression gate: tests/test_regressions.py*
*Run: pytest tests/ -v --timeout=60*

View File

@@ -59,6 +59,55 @@ def _get_ai_agent():
except ImportError:
pass
return AIAgent
def _aiagent_import_error_detail() -> str:
"""Return a multi-line diagnostic string for the "AIAgent not available" path.
The bare ImportError ("AIAgent not available -- check that hermes-agent is
on sys.path") leaves users guessing at which python is running, where it's
looking, and what to fix. We assemble the same evidence a maintainer would
ask for first (issue #1695): the python that's running, the agent_dir env
var if set, the sys.path entries that mention 'hermes', and the most-common
fix (`pip install -e .` in the agent dir).
Kept as a separate helper so it stays out of the hot path until we actually
need to raise — building it on every successful import would be wasted work.
"""
import os as _os
import sys as _sys
lines = ["AIAgent not available -- check that hermes-agent is on sys.path"]
lines.append("")
lines.append(f" python: {_sys.executable}")
agent_dir = _os.environ.get("HERMES_WEBUI_AGENT_DIR")
if agent_dir:
lines.append(f" HERMES_WEBUI_AGENT_DIR: {agent_dir}")
else:
lines.append(" HERMES_WEBUI_AGENT_DIR: (not set)")
# Show only the sys.path entries that look relevant — full sys.path is noisy.
relevant = [p for p in _sys.path if "hermes" in p.lower() or "agent" in p.lower()]
if relevant:
lines.append(" sys.path entries mentioning hermes/agent:")
for entry in relevant[:6]:
lines.append(f" - {entry}")
if len(relevant) > 6:
lines.append(f" ... and {len(relevant) - 6} more")
else:
lines.append(" sys.path: (no entries mention hermes or agent)")
lines.append("")
lines.append(" Most common fix: install the agent in editable mode so its modules")
lines.append(" appear on sys.path:")
lines.append("")
lines.append(" cd /path/to/hermes-agent")
lines.append(" pip install -e .")
lines.append("")
lines.append(" Then restart the WebUI.")
lines.append("")
lines.append(' Full troubleshooting: docs/troubleshooting.md ("AIAgent not available")')
return "\n".join(lines)
from api.models import get_session, title_from
from api.workspace import set_last_workspace
@@ -1958,7 +2007,7 @@ def _run_agent_streaming(
_AIAgent = _get_ai_agent()
if _AIAgent is None:
raise ImportError("AIAgent not available -- check that hermes-agent is on sys.path")
raise ImportError(_aiagent_import_error_detail())
# Initialize SessionDB so session_search works in WebUI sessions
_session_db = None

97
docs/troubleshooting.md Normal file
View File

@@ -0,0 +1,97 @@
# Troubleshooting
Concrete diagnostic flows for the most common failure modes when running Hermes WebUI. Each entry has the symptom, the diagnostic commands you should run *before* opening an issue, and the fix that has worked for past reporters.
If your symptom isn't listed and the diagnostics don't narrow it down, file a bug at https://github.com/nesquena/hermes-webui/issues — include the **full output** of every command in the relevant section.
---
## "AIAgent not available -- check that hermes-agent is on sys.path"
**Symptom.** WebUI starts, shows the chat interface, but every chat request fails immediately with this error in the response or the server log. As of v0.51.6 the error includes a diagnostic block with the running Python interpreter, the relevant `sys.path` entries, and the most-common fix; on older versions the message is bare.
**Why it happens.** The WebUI imports the agent class at chat time via `from run_agent import AIAgent`. That import only succeeds if the running Python's `sys.path` contains either the hermes-agent checkout or a pip-installed copy of the agent. Three common failure modes:
1. **Agent installed but not on `sys.path`.** Most common. The agent is checked out somewhere (e.g. `~/Programmes/hermes-agent`), the WebUI was launched with a Python that doesn't know about it, and there's no `pip install -e .` linking the two.
2. **Symlink with a typo or wrong target.** A symlink to the agent looks correct on `ls`, but `readlink` resolves to a path that doesn't exist or doesn't contain `agent/__init__.py`.
3. **`HERMES_WEBUI_AGENT_DIR` set to the wrong directory.** Override env var beats auto-discovery and points at a directory that has no agent code.
### Step 1 — confirm the agent location
```bash
# If you have ~/hermes-agent (the default location):
ls -la ~/hermes-agent
readlink ~/hermes-agent # if it's a symlink, where does it resolve?
ls ~/hermes-agent/agent/__init__.py 2>&1
```
The third command must succeed (the file must exist). If it fails, your symlink is broken or pointing at a directory that's missing the agent module — fix that first.
### Step 2 — confirm the WebUI is using the right Python
```bash
cd ~/hermes-webui && ./start.sh 2>&1 | grep -iE 'agent|python|hermes_webui_python' | head -20
```
The startup banner prints which Python and agent dir it resolved. If the agent dir is empty or the Python is the wrong one, set the override:
```bash
export HERMES_WEBUI_AGENT_DIR=/absolute/path/to/hermes-agent
export HERMES_WEBUI_PYTHON=/absolute/path/to/agent/venv/bin/python
./start.sh
```
### Step 3 — install the agent in editable mode
This is the most common fix and resolves the original issue #1695:
```bash
cd /path/to/hermes-agent # the directory holding pyproject.toml + the agent/ module
pip install -e . # use the same python that runs the WebUI
```
Then restart the WebUI:
```bash
cd ~/hermes-webui
./start.sh
```
### Step 4 — verify by importing manually
If steps 1-3 still don't work, check whether the WebUI's Python can import the agent at all:
```bash
$HERMES_WEBUI_PYTHON -c "from run_agent import AIAgent; print('ok')" 2>&1
```
(Replace `$HERMES_WEBUI_PYTHON` with the actual Python path from step 2 if the env var isn't set.) If this prints `ok`, the agent IS on `sys.path` for that Python — and the WebUI should work.
If this fails, `import run_agent` itself is broken — check that the agent's pyproject.toml lists `run_agent` as a top-level module or that the agent dir is on PYTHONPATH:
```bash
PYTHONPATH=/path/to/hermes-agent $HERMES_WEBUI_PYTHON -c "from run_agent import AIAgent; print('ok')"
```
If adding PYTHONPATH fixes it, persist the path either via `pip install -e .` (preferred) or by setting `HERMES_WEBUI_AGENT_DIR` to that directory.
### When to file a bug
If after running steps 1-4 the import still fails *and* `pip install -e .` succeeded *and* `PYTHONPATH=... python -c "from run_agent import AIAgent"` succeeds — that's a real WebUI bug. File at https://github.com/nesquena/hermes-webui/issues with:
- The output of every command in steps 1-4
- The full diagnostic block printed by the WebUI's `ImportError` (v0.51.6+)
- Your OS, Python version, and how the agent was installed
---
## Other troubleshooting
This document grows over time. If a recurring failure mode isn't covered here yet, add it via PR. The format for each entry: **Symptom → Why → Diagnostic commands → Fix → When to file a bug**.
Related references:
- [`docs/supervisor.md`](supervisor.md) — process-supervisor setup (launchd, systemd, supervisord, runit/s6) including the bootstrap supervisor-foreground flag.
- [`docs/docker.md`](docker.md) — Docker compose setup, common failure modes, bind-mount migration.
- [`docs/wsl-autostart.md`](wsl-autostart.md) — WSL2 auto-start at login on Windows.
- [`docs/EXTENSIONS.md`](EXTENSIONS.md) — WebUI extension injection, security model, examples.

View File

@@ -0,0 +1,184 @@
"""Tests for #1695 — diagnostic detail in the "AIAgent not available" ImportError.
Patrick-81 reported a symlinked hermes-agent install that produced a bare
"AIAgent not available -- check that hermes-agent is on sys.path" error with
no information about which Python was running, where it was looking, or what
to do next. The maintainer's response (which Patrick confirmed worked)
amounted to: run three diagnostic commands, then `pip install -e .` in the
agent dir.
This test suite locks the diagnostic shape of the new error message:
- The original message string is preserved (so existing log scrapers /
monitoring / docs-search keep working).
- The running python interpreter path is included.
- HERMES_WEBUI_AGENT_DIR is shown if set, "(not set)" otherwise.
- The relevant sys.path entries are shown.
- A pip install -e . hint is included.
- A pointer to docs/troubleshooting.md is included.
Behavioural test for the actual raise path lives in the streaming integration
suite; this file only exercises the helper.
"""
import os
import sys
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parents[1]
def _import_helper():
"""Import _aiagent_import_error_detail without triggering the full streaming
module side-effects.
api/streaming.py imports a lot at top-level (gateway routing, model resolver,
session DB, ...). For a focused unit test we just need the helper. Importing
the module is fine — it stays cached for the rest of the suite.
"""
sys.path.insert(0, str(REPO_ROOT))
from api import streaming # noqa: F401
return streaming._aiagent_import_error_detail
class TestAIAgentImportErrorDetail:
"""Unit tests for the diagnostic helper."""
def test_preserves_original_message_for_log_scrapers(self):
"""The original error string must remain the FIRST line so existing
scrapers, alerting, and docs-search keep matching.
"""
helper = _import_helper()
out = helper()
first = out.splitlines()[0]
assert first == "AIAgent not available -- check that hermes-agent is on sys.path", (
f"first line must be the original error message verbatim, got: {first!r}"
)
def test_includes_running_python_interpreter(self):
"""The diagnostic must include the running python so the user knows
which interpreter is missing the agent (most common cause of the bug).
"""
helper = _import_helper()
out = helper()
assert "python:" in out
assert sys.executable in out, (
f"running python ({sys.executable}) must appear in the diagnostic"
)
def test_shows_agent_dir_env_when_set(self, monkeypatch):
"""If HERMES_WEBUI_AGENT_DIR is set, the diagnostic must show its value
so the user can confirm whether the override is pointing at the right
directory.
"""
helper = _import_helper()
monkeypatch.setenv("HERMES_WEBUI_AGENT_DIR", "/custom/agent/path")
out = helper()
assert "HERMES_WEBUI_AGENT_DIR: /custom/agent/path" in out
def test_shows_agent_dir_env_unset_marker(self, monkeypatch):
"""If HERMES_WEBUI_AGENT_DIR is NOT set, the diagnostic must say so
explicitly — silence is ambiguous (could be empty string, could be unset).
"""
helper = _import_helper()
monkeypatch.delenv("HERMES_WEBUI_AGENT_DIR", raising=False)
out = helper()
assert "HERMES_WEBUI_AGENT_DIR: (not set)" in out
def test_includes_pip_install_editable_hint(self):
"""The most common fix (per #1695) is `pip install -e .` in the agent dir.
The diagnostic must surface this as the first-line remediation.
"""
helper = _import_helper()
out = helper()
assert "pip install -e ." in out, (
"diagnostic must surface `pip install -e .` as the most common fix"
)
def test_points_at_troubleshooting_doc(self):
"""The diagnostic must point at the docs/troubleshooting.md entry so
users with edge-case failures know where to look next.
"""
helper = _import_helper()
out = helper()
assert "troubleshooting" in out.lower(), (
"diagnostic must point at docs/troubleshooting.md for further help"
)
def test_lists_sys_path_entries_when_relevant(self, monkeypatch):
"""If sys.path contains entries mentioning hermes/agent, the diagnostic
must list them (helps the user confirm the agent dir is or isn't
actually present on the import path).
"""
helper = _import_helper()
# Force at least one relevant entry into sys.path for the test.
monkeypatch.syspath_prepend("/fake/hermes-agent")
out = helper()
assert "/fake/hermes-agent" in out
def test_handles_no_relevant_sys_path_entries(self, monkeypatch):
"""If sys.path has NO hermes/agent-related entries, the diagnostic must
say so explicitly — this is itself a strong diagnostic signal.
"""
helper = _import_helper()
# Replace sys.path with entries that mention neither hermes nor agent.
# Use monkeypatch.setattr so the change reverts cleanly.
clean_path = ["/usr/lib/python3.11", "/usr/local/lib/python3.11", "/tmp"]
monkeypatch.setattr(sys, "path", clean_path)
out = helper()
assert "no entries mention hermes or agent" in out, (
"diagnostic must explicitly call out empty-path case (it's a strong signal)"
)
def test_output_is_multiline_string(self):
"""The diagnostic must be a multi-line string (newline-joined), not a
single long line — log-readability matters when this surfaces in a
traceback.
"""
helper = _import_helper()
out = helper()
assert "\n" in out, "diagnostic must be multi-line for log readability"
assert len(out.splitlines()) >= 5, (
f"diagnostic must have at least 5 lines, got {len(out.splitlines())}"
)
class TestAIAgentImportErrorDocsPresence:
"""Regression: the docs/troubleshooting.md file must exist with the
"AIAgent not available" entry the diagnostic links to.
"""
def test_troubleshooting_md_exists(self):
path = REPO_ROOT / "docs" / "troubleshooting.md"
assert path.exists(), "docs/troubleshooting.md must exist (referenced by streaming.py)"
def test_troubleshooting_md_has_aiagent_section(self):
path = REPO_ROOT / "docs" / "troubleshooting.md"
content = path.read_text(encoding="utf-8")
assert "AIAgent not available" in content, (
"docs/troubleshooting.md must have an entry titled \"AIAgent not available\""
)
def test_troubleshooting_md_includes_pip_install_editable(self):
"""The doc must surface the `pip install -e .` fix."""
path = REPO_ROOT / "docs" / "troubleshooting.md"
content = path.read_text(encoding="utf-8")
assert "pip install -e ." in content, (
"docs/troubleshooting.md must include the pip install -e . fix"
)
def test_troubleshooting_md_describes_diagnostic_steps(self):
"""The doc must walk through diagnostic commands (readlink, ls, etc.)
before jumping to the fix — that ordering is what worked for #1695.
"""
path = REPO_ROOT / "docs" / "troubleshooting.md"
content = path.read_text(encoding="utf-8")
# Look for the symlink-resolution diagnostic chain.
assert "readlink" in content, (
"diagnostic flow must include `readlink` for the symlink-typo failure mode"
)
assert "/agent/__init__.py" in content, (
"diagnostic flow must verify the agent module file is reachable"
)