- *Python on Debian 13 is PEP 668.* System pip is blocked. The installer
deliberately installs deps into the Python that runs Hermes
(=PYTHON= env override, =$HERMES_HOME/hermes-agent/venv/bin/python3=,
- or the =hermes= launcher shebang), and falls back to
- =--break-system-packages= only inside that interpreter. The standalone
- tests use the project =.venv=. Do not pip-install into the system
- Python from this project.
+ or the =hermes= launcher shebang). That interpreter is a uv-created venv
+ without pip (see pitfall below), so dependency installation tries pip,
+ then =ensurepip=-bootstrapped pip, then =uv= from =PATH= or
+ =$HERMES_HOME/bin/uv=. Do not pip-install into the system Python from
+ this project.
- *Filenames with spaces.* Everything under =Tools/= contains spaces —
quote paths in shell commands.
- *The =Tools/Open with IntelliJ IDEA= helper* opens this tree as an
Hermes at =~/.hermes/bin/uv=), then falls back to the Hermes runtime
venv. All =Tools/= scripts source it — never open-code venv handling
in a Tools script.
+- *Pitfall: the Hermes runtime venv has no pip.* Hermes's own installer
+ creates =~/.hermes/hermes-agent/venv= with =uv venv=, which does not seed
+ pip — =venv/bin/python3 -m pip= dies with =No module named pip= (found
+ 2026-08-30 installing Retinue on a fresh Debian 13 machine: both pip
+ attempts failed, and the suggested manual =pip install= fix would have
+ failed the same way). The installer now chains pip → ensurepip → uv
+ (=$HERMES_HOME/bin/uv=, probed explicitly because non-login shells have a
+ minimal PATH) and prints captured attempt output on failure instead of
+ hiding it behind =2>/dev/null=. Keep every one of those fallbacks.
+- *Pitfall: silent failure via =2>/dev/null=.* The 2026-08-30 install
+ failure was undebuggable because the installer discarded stderr. Every
+ fallible step (deps, model preload, =hermes config set=, plugin
+ validation) now captures combined output into a temp dir (=DIAG_DIR=,
+ cleaned by an EXIT trap) and prints a =show_log_tail= excerpt on
+ failure. Never reintroduce bare =2>/dev/null= on a step that can fail.
- *Pitfall: gateway restart on the remote.* =systemctl --user restart
hermes-gateway.service= can leave the unit in =activating= when an old
gateway process (started with =gateway run --replace= outside systemd)
set -euo pipefail
+# Diagnostics: every step that can fail captures its combined output here and
+# prints it on failure. Never hide stderr behind 2>/dev/null — a silent
+# failure on an unfamiliar host is undebuggable (found 2026-08-30: a pip-less
+# Hermes venv failed the install with zero visible reason).
+DIAG_DIR="$(mktemp -d)"
+trap 'rm -rf "$DIAG_DIR"' EXIT
+
+# Print the last lines of a captured log, tolerating tqdm-style \r progress
+# spam (model downloads) that would otherwise arrive as one giant line.
+show_log_tail() { # show_log_tail <file> <n-lines>
+ tr '\r' '\n' <"$1" | tail -n "${2:-15}" | sed 's/^/ /'
+}
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SOURCE_DIR="$SCRIPT_DIR"
# Ensure Python dependencies are available in the target interpreter.
# Hermes itself runs with this Python, so the deps must be importable there.
+#
+# The Hermes runtime venv is created by uv, which does NOT seed pip into it —
+# "$PYTHON -m pip" fails there with "No module named pip". On bare Debian 13
+# hosts ensurepip is also missing (no python3.X-venv apt package), so the
+# reliable path is the uv binary Hermes itself installed at
+# $HERMES_HOME/bin/uv. Try every available installer in order and capture
+# each attempt's output so a total failure can show real diagnostics
+# instead of failing silently.
ensure_python_deps() {
local missing=""
if ! "$PYTHON" -c "import sqlite_vec" 2>/dev/null; then
fi
echo "Missing Python dependencies:$missing"
- echo "Installing with $PYTHON -m pip ..."
- if "$PYTHON" -m pip install $missing 2>/dev/null; then
- echo "Dependencies installed successfully."
- return 0
+ local logdir attempt=0
+ logdir="$(mktemp -d)"
+
+ try_install() { # try_install <label> <command...>
+ local label="$1"; shift
+ attempt=$((attempt + 1))
+ echo "Installing with $label ..."
+ if "$@" >"$logdir/attempt-$attempt.log" 2>&1; then
+ echo "Dependencies installed successfully ($label)."
+ rm -rf "$logdir"
+ return 0
+ fi
+ return 1
+ }
+
+ if "$PYTHON" -m pip --version >/dev/null 2>&1; then
+ try_install "$PYTHON -m pip" \
+ "$PYTHON" -m pip install $missing && return 0
+ try_install "$PYTHON -m pip --break-system-packages" \
+ "$PYTHON" -m pip install --break-system-packages $missing && return 0
+ else
+ echo " ($PYTHON has no pip module — typical for uv-created Hermes venvs)"
+ # Bootstrap pip via ensurepip, then use it. Fails harmlessly on hosts
+ # without the python3.X-venv apt package (no bundled pip wheel).
+ if "$PYTHON" -m ensurepip --upgrade >"$logdir/ensurepip.log" 2>&1; then
+ try_install "$PYTHON -m pip (bootstrapped via ensurepip)" \
+ "$PYTHON" -m pip install $missing && return 0
+ fi
fi
- if "$PYTHON" -m pip install --break-system-packages $missing 2>/dev/null; then
- echo "Dependencies installed successfully (with --break-system-packages)."
- return 0
+ # uv can install into any interpreter without pip. Hermes bundles its own
+ # uv at $HERMES_HOME/bin/uv, and installers often run over non-login SSH
+ # where PATH is minimal — probe both.
+ local UV
+ UV="$(command -v uv 2>/dev/null || true)"
+ [[ -z "$UV" && -x "$HERMES_HOME/bin/uv" ]] && UV="$HERMES_HOME/bin/uv"
+ if [[ -n "$UV" ]]; then
+ try_install "$UV pip install --python $PYTHON" \
+ "$UV" pip install --python "$PYTHON" $missing && return 0
+ try_install "$UV pip install --break-system-packages --python $PYTHON" \
+ "$UV" pip install --break-system-packages --python "$PYTHON" $missing && return 0
fi
echo "ERROR: Failed to install dependencies."
- echo " Install them manually:"
- echo " $PYTHON -m pip install --break-system-packages sqlite-vec model2vec"
+ local f
+ for f in "$logdir"/*.log; do
+ [[ -s "$f" ]] || continue
+ echo "--- output of $(basename "$f" .log) (last lines):"
+ tail -n 15 "$f" | sed 's/^/ /'
+ done
+ rm -rf "$logdir"
+ echo " Install them manually with uv (bundled with Hermes):"
+ echo " $HERMES_HOME/bin/uv pip install --python $PYTHON sqlite-vec model2vec"
+ echo " or bootstrap pip into the interpreter first:"
+ echo " $PYTHON -m ensurepip --upgrade"
+ echo " $PYTHON -m pip install sqlite-vec model2vec"
return 1
}
preload_embedding_model() {
echo "Pre-downloading embedding model (minishlab/potion-multilingual-128M)..."
export HF_HOME="$PLUGIN_DIR/.hf-cache"
+ local log="$DIAG_DIR/preload-model.log"
if "$PYTHON" -c "
import sys
sys.path.insert(0, '$PLUGIN_DIR/src')
from retinue.memory import get_model
get_model()
print(' Embedding model ready.')
-" 2>/dev/null; then
+" >"$log" 2>&1; then
+ show_log_tail "$log" 3
echo " Model cached at $PLUGIN_DIR/.hf-cache"
else
echo " Warning: could not pre-download the embedding model."
+ echo " --- last lines of output:"
+ show_log_tail "$log" 20
echo " It will download on first use inside Hermes."
echo " (Check network / Hugging Face connectivity.)"
fi
if [[ -n "$HERMES_CLI" ]]; then
echo "Setting memory.provider to retinue (via $HERMES_CLI)..."
- if "$HERMES_CLI" config set memory.provider retinue 2>/dev/null; then
+ if "$HERMES_CLI" config set memory.provider retinue >"$DIAG_DIR/config-set.log" 2>&1; then
echo " memory.provider set to retinue."
else
echo " Warning: could not set memory.provider automatically."
+ echo " --- hermes config set output:"
+ show_log_tail "$DIAG_DIR/config-set.log"
echo " Set it manually in $HERMES_HOME/config.yaml:"
echo " memory:"
echo " provider: retinue"
print('name:', p.name)
print('available:', p.is_available())
print('schemas:', [s['name'] for s in p.get_tool_schemas()])
-" 2>/dev/null; then
+" >"$DIAG_DIR/validate.log" 2>&1; then
+ show_log_tail "$DIAG_DIR/validate.log"
echo "Plugin validation passed."
else
echo "Plugin validation failed. Dependencies may be missing."
+ echo "--- validation output:"
+ show_log_tail "$DIAG_DIR/validate.log" 25
echo "Install them with:"
- echo " $PYTHON -m pip install sqlite-vec model2vec"
+ echo " $HERMES_HOME/bin/uv pip install --python $PYTHON sqlite-vec model2vec"
+ echo " # or: $PYTHON -m pip install sqlite-vec model2vec"
echo "The embedding model (~500 MB) will download on first use."
exit 1
fi