Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
69f133c
feat(hermes): update runtime to 0.20.6
prekshivyas Aug 29, 2026
1c184c0
fix(hermes): repair 0.20.6 base build
prekshivyas Aug 29, 2026
b641064
fix(hermes): expose venv to sandbox probe
prekshivyas Aug 29, 2026
fda1835
fix(hermes): pin base venv interpreter
prekshivyas Aug 29, 2026
3608065
fix(hermes): pin lazy browser runtime
prekshivyas Aug 29, 2026
2bbd3d3
fix(hermes): initialize MCP image probe
prekshivyas Aug 29, 2026
37c47df
fix(hermes): pin 0.20.6 base image
prekshivyas Aug 29, 2026
25f6077
fix(hermes): verify resolved cron ledger path
prekshivyas Aug 29, 2026
8b89546
fix(hermes): bind layered browser policy source
prekshivyas Aug 29, 2026
92f49da
fix(hermes): bind venv interpreter link
prekshivyas Aug 29, 2026
97bdee4
fix(hermes): close migration review findings
prekshivyas Aug 29, 2026
6a7aafa
fix(hermes): remove duplicate final image setup
prekshivyas Aug 29, 2026
732dba8
fix(hermes): align portable image context
prekshivyas Aug 29, 2026
7ebc0bf
fix(hermes): complete managed image validation
prekshivyas Aug 29, 2026
7d540d4
fix(hermes): close review evidence gaps
prekshivyas Aug 29, 2026
997666e
fix(hermes): simplify browser cache validation
prekshivyas Aug 29, 2026
619d344
refactor(hermes): remove redundant browser manifest
prekshivyas Aug 29, 2026
a8a5fba
fix(hermes): close runtime fallback gaps
prekshivyas Aug 29, 2026
4f24bf1
fix(hermes): normalize final image repairs
prekshivyas Aug 30, 2026
2095221
fix(hermes): remove build-only migration patchers
prekshivyas Aug 30, 2026
28ca8c8
fix(hermes): stay within managed image layer budget
prekshivyas Aug 30, 2026
13574de
fix(hermes): scope one-shot completion wait
prekshivyas Aug 30, 2026
7d16555
fix(hermes): pin repaired base image
prekshivyas Aug 30, 2026
c52e3da
merge: sync Hermes upgrade with main
prekshivyas Aug 30, 2026
14cdb62
fix(rebuild): verify replacement agent version
prekshivyas Aug 30, 2026
65d02b7
fix(hermes): trim portable build context
prekshivyas Aug 30, 2026
ec0f8ca
fix(hermes): avoid plugin discovery deadlock
prekshivyas Aug 30, 2026
59b3936
Merge branch 'main' into codex/hermes-0206-upgrade
prekshivyas Aug 30, 2026
4f4bae5
Merge branch 'main' into codex/hermes-0206-upgrade
prekshivyas Aug 30, 2026
e384bcf
fix(hermes): repair 0.20.6 E2E regressions
prekshivyas Aug 31, 2026
216ba8b
merge(main): update Hermes upgrade branch
prekshivyas Aug 31, 2026
42471ab
fix(hermes): repair 0.20.6 E2E regressions
prekshivyas Aug 31, 2026
128522e
merge(main): update Hermes upgrade branch
prekshivyas Aug 31, 2026
08aad07
test(e2e): close Hermes fixture gaps
prekshivyas Aug 31, 2026
3411f78
fix(hermes): repair deterministic E2E regressions
prekshivyas Aug 31, 2026
e626167
fix(hermes): preserve cron recovery gate time
prekshivyas Aug 31, 2026
0efcebe
fix(e2e): repair Hermes 0.20.6 fixture assertions
prekshivyas Aug 31, 2026
9d2e195
Merge branch 'main' into codex/hermes-0206-upgrade
prekshivyas Aug 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/resolve-hermes-base-image/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ runs:
--read-only \
--user sandbox \
--entrypoint /opt/hermes/.venv/bin/python "$ref" -I -c \
'import importlib.metadata as metadata; import sys; import acp; import mcp; from acp_adapter.server import HermesACPAgent; from tools import mcp_tool; metadata.version("agent-client-protocol") == "0.9.0" or sys.exit(1); getattr(mcp_tool, "_MCP_AVAILABLE", False) or sys.exit(1); getattr(mcp_tool, "_MCP_HTTP_AVAILABLE", False) or sys.exit(1)' \
'import importlib.metadata as metadata; import sys; import acp; import mcp; from acp_adapter.server import HermesACPAgent; from tools import mcp_tool; metadata.version("agent-client-protocol") == "0.9.0" or sys.exit(1); mcp_tool._ensure_mcp_sdk() or sys.exit(1); getattr(mcp_tool, "_MCP_AVAILABLE", False) or sys.exit(1); getattr(mcp_tool, "_MCP_HTTP_AVAILABLE", False) or sys.exit(1)' \
>/dev/null 2>&1
}

Expand Down
248 changes: 134 additions & 114 deletions agents/hermes/Dockerfile

Large diffs are not rendered by default.

96 changes: 76 additions & 20 deletions agents/hermes/Dockerfile.base
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ ENV DEBIAN_FRONTEND=noninteractive
# pyproject.toml, HERMES_TARBALL_SHA256 the GitHub tarball checksum, and
# HERMES_NPM_INTEGRITY the `npm view hermes-agent@<semver> dist.integrity`
# sha512 used as a registry cross-check at build time.
# Calver tag v2026.7.20 = Hermes Agent v0.19.0.
ARG HERMES_VERSION=v2026.7.20
ARG HERMES_SEMVER=0.19.0
ARG HERMES_TARBALL_SHA256=285f3fc134ff466a90065e1517801a68993733b807158ee8f32aa01613786990
ARG HERMES_NPM_INTEGRITY=sha512-+oVKG3lXbk2kEP+J6BXZjtmSBSaFfczIdOWQ9CUSTdTqq2uyHbk4p+kPyZ6MeGs56JU5qXzMNbqGKRVOQRGC1A==
# Calver tag v2026.8.27 = Hermes Agent v0.20.6.
ARG HERMES_VERSION=v2026.8.27
ARG HERMES_SEMVER=0.20.6
ARG HERMES_TARBALL_SHA256=e622723b5bf3cd6c1db974d92d32242f1cb63f61c1112b6f708b34d619ef0fc7
ARG HERMES_NPM_INTEGRITY=sha512-s5q1IEBifCBb77QMwkse4MRaAaoZSxIa4IkicIO3jL7MIdq15YvnSyiNvsTOWNBi6t3shFpIg+H7+9MJsOiSkg==
ARG HERMES_UV_EXTRAS="anthropic messaging web pty mcp acp"
ARG NODE_VERSION=24.18.1
ARG UV_VERSION=0.11.33
Expand Down Expand Up @@ -424,7 +424,10 @@ RUN printf '%s\n' \
# final image when selected.
# New Hermes integrations should be installed by the agent workflow when they
# are enabled rather than shipped in the base image by default.
# Root Node dependencies provide Hermes browser tooling such as agent-browser.
# Hermes 0.20.6 deliberately keeps agent-browser outside the root workspace
# graph and resolves the exact NemoClaw-pinned package through npx. Warm that
# package in the sandbox user's runtime cache below so browser startup does not
# require a registry fetch.
# The WhatsApp adapter ships a separate Node project under
# scripts/whatsapp-bridge whose dependencies Hermes otherwise installs lazily
# on the first `hermes whatsapp` run. That lazy `npm install` targets
Expand All @@ -443,6 +446,7 @@ RUN pip3 install --no-cache-dir --break-system-packages "uv==${UV_VERSION}" \
# intentionally hostile security-test fixtures. Remove them in the extraction
# RUN so their bytes never enter a published image layer.
COPY agents/hermes/security-dependencies.patch /tmp/hermes-security-dependencies.patch
COPY agents/hermes/agent-browser-runtime/package-lock.json /tmp/nemoclaw-agent-browser-runtime/package-lock.json
COPY agents/hermes/whatsapp-proxy.patch /tmp/hermes-whatsapp-proxy.patch
COPY scripts/checks/download-hermes-source-archive.sh /tmp/nemoclaw-download-hermes-source-archive.sh
RUN mkdir -p /opt/hermes \
Expand Down Expand Up @@ -498,16 +502,20 @@ WORKDIR /opt/hermes
# workspaces disabled; this keeps browser tooling without either UI build tree.
# Root npm, Electron, and node-gyp caches are build-only. Remove them in the
# same RUN that creates them so Docker cannot retain their bytes in this layer.
# Hermes ships a `.python-version` for Python 3.11. Use the reviewed system
# Python 3.13 directly and forbid a root-private uv-managed interpreter so the
# finished virtual environment remains executable by the sandbox user.
# hadolint ignore=SC2086
RUN set -eu; \
set --; \
for extra in ${HERMES_UV_EXTRAS}; do \
set -- "$@" --extra "$extra"; \
done; \
uv sync --frozen --no-dev "$@" --no-cache \
uv sync --python /usr/bin/python3.13 --no-managed-python \
--frozen --no-dev "$@" --no-cache \
&& uv pip check --python /opt/hermes/.venv/bin/python \
&& /opt/hermes/.venv/bin/python -I -c \
"from importlib.metadata import version; expected = {'agent-client-protocol': '0.9.0', 'aiohttp': '3.14.3', 'cryptography': '50.0.0', 'mcp': '1.28.1', 'pillow': '12.3.0', 'starlette': '1.3.1', 'tornado': '6.5.7'}; actual = {name: version(name) for name in expected}; assert actual == expected, actual" \
"from importlib.metadata import version; expected = {'agent-client-protocol': '0.9.0', 'aiohttp': '3.14.3', 'cryptography': '50.0.0', 'mcp': '2.0.0', 'pillow': '12.3.0', 'starlette': '1.3.1', 'tornado': '6.5.7'}; actual = {name: version(name) for name in expected}; assert actual == expected, actual" \
&& npm ci --prefer-offline --no-audit --no-fund \
&& for ui_dir in ui-tui web; do \
if [ -f "${ui_dir}/package-lock.json" ]; then \
Expand Down Expand Up @@ -549,11 +557,11 @@ RUN set -eu; \
# route that uses File/Form, so without python-multipart the plugin's API routes
# fail to mount ("Form data requires python-multipart to be installed").
#
# Hermes 0.19.0 resolves 0.0.27, which is affected by the network-reachable
# GHSA-5rvq-cxj2-64vf CPU denial of service and GHSA-6jv3-5f52-599m parser
# differential. Keep this hash-verified downstream override at the first stable
# release that fixes those issues plus GHSA-v9pg-7xvm-68hf. Re-review the
# version and both hashes on every Hermes version bump.
# Hermes 0.20.6 resolves 0.0.32, the first stable release that addresses the
# reviewed network-reachable GHSA-5rvq-cxj2-64vf CPU denial of service,
# GHSA-6jv3-5f52-599m parser differential, and GHSA-v9pg-7xvm-68hf. Keep the
# hash-verified install so the final image cannot inherit a stale published
# base. Re-review the version and both hashes on every Hermes version bump.
# uv creates a phony .git cache marker even with --no-cache. Remove the cache
# after the final uv command because the root cache is not used at runtime.
# hadolint ignore=DL3059
Expand All @@ -569,6 +577,10 @@ RUN printf '%s\n' \
"import multipart; assert multipart.__version__ == '0.0.32', multipart.__version__" \
&& rm -rf /root/.cache/uv

# The offline lazy-package probe runs as the sandbox user. Make the finalized
# virtual environment readable before that unprivileged build-time check.
RUN chmod -R a+rX /opt/hermes/.venv

# Keep official-package compatibility separate from the final image's offline
# lazy-installer contract. BuildKit verifies both reviewed PyPI artifacts before
# any build command can consume them. Install only those hash-locked wheels into
Expand Down Expand Up @@ -607,12 +619,57 @@ RUN --network=none install -d -o sandbox -g sandbox -m 0750 \
ENV PATH="/usr/local/bin:/opt/hermes/.venv/bin:${PATH}" \
HERMES_TUI_DIR="/opt/hermes/ui-tui" \
HERMES_WEB_DIST="/opt/hermes/hermes_cli/web_dist"

# Hermes 0.20.6 removed agent-browser from its root dependency graph. Prove
# that the patched source selects the exact npx fallback. Cache the exact
# registry package, bind its cached integrity to the reviewed lockfile, then
# prove the cached package works without network access.
RUN HOME=/sandbox \
/usr/bin/setpriv --reuid=sandbox --regid=sandbox --init-groups -- \
/opt/hermes/.venv/bin/python -c \
'from tools import browser_tool; assert browser_tool.AGENT_BROWSER_NPX_SPEC == "agent-browser@0.26.0"; assert browser_tool._find_agent_browser() == browser_tool.NPX_AGENT_BROWSER_SENTINEL'
RUN chown -R sandbox:sandbox /tmp/nemoclaw-agent-browser-runtime \
&& install -o sandbox -g sandbox -m 0400 /dev/null \
/tmp/nemoclaw-agent-browser-runtime/npm-userconfig \
&& install -o sandbox -g sandbox -m 0400 /dev/null \
/tmp/nemoclaw-agent-browser-runtime/npm-globalconfig \
&& HOME=/sandbox npm_config_cache=/sandbox/.npm \
npm_config_registry=https://registry.npmjs.org/ \
npm_config_userconfig=/tmp/nemoclaw-agent-browser-runtime/npm-userconfig \
npm_config_globalconfig=/tmp/nemoclaw-agent-browser-runtime/npm-globalconfig \
npm_config_ignore_scripts=true \
/usr/bin/setpriv --reuid=sandbox --regid=sandbox --init-groups -- \
/usr/local/bin/npm cache add agent-browser@0.26.0 \
&& locked_browser_integrity="$( \
/usr/bin/setpriv --reuid=sandbox --regid=sandbox --init-groups -- \
/usr/local/bin/node -e \
'const fs = require("node:fs"); const lock = JSON.parse(fs.readFileSync("/tmp/nemoclaw-agent-browser-runtime/package-lock.json", "utf8")); const integrity = lock.packages?.["node_modules/agent-browser"]?.integrity; if (typeof integrity !== "string" || integrity.length === 0) process.exit(1); process.stdout.write(integrity)' \
)" \
&& cached_browser_integrity="$( \
HOME=/sandbox npm_config_cache=/sandbox/.npm npm_config_offline=true \
npm_config_registry=https://registry.npmjs.org/ \
npm_config_userconfig=/tmp/nemoclaw-agent-browser-runtime/npm-userconfig \
npm_config_globalconfig=/tmp/nemoclaw-agent-browser-runtime/npm-globalconfig \
npm_config_ignore_scripts=true \
/usr/bin/setpriv --reuid=sandbox --regid=sandbox --init-groups -- \
/usr/local/bin/npm view agent-browser@0.26.0 dist.integrity \
)" \
&& test "${cached_browser_integrity}" = "${locked_browser_integrity}"
RUN --network=none agent_browser_version="$( \
HOME=/sandbox npm_config_cache=/sandbox/.npm npm_config_offline=true \
npm_config_registry=https://registry.npmjs.org/ \
npm_config_userconfig=/tmp/nemoclaw-agent-browser-runtime/npm-userconfig \
npm_config_globalconfig=/tmp/nemoclaw-agent-browser-runtime/npm-globalconfig \
npm_config_ignore_scripts=true \
/usr/bin/setpriv --reuid=sandbox --regid=sandbox --init-groups -- \
/usr/local/bin/npx --ignore-scripts --prefer-offline -y \
agent-browser@0.26.0 --version \
)" \
&& test "${agent_browser_version}" = 'agent-browser 0.26.0' \
&& rm -rf /tmp/nemoclaw-agent-browser-runtime

RUN /usr/local/bin/hermes --version \
&& /usr/local/bin/hermes acp --check \
&& test -x /opt/hermes/node_modules/.bin/agent-browser \
&& /opt/hermes/node_modules/.bin/agent-browser --version \
&& /opt/hermes/.venv/bin/python -c \
'from tools import browser_tool; expected = "/opt/hermes/node_modules/.bin/agent-browser"; assert browser_tool._find_agent_browser() == expected' \
&& test -s "${HERMES_TUI_DIR}/dist/entry.js" \
&& test -s "${HERMES_WEB_DIST}/index.html" \
&& test ! -e /opt/hermes/.node_modules.runtime \
Expand All @@ -622,10 +679,9 @@ RUN /usr/local/bin/hermes --version \
&& rmdir /opt/hermes/node_modules \
&& mv /opt/hermes/.node_modules.runtime /opt/hermes/node_modules \
&& /opt/hermes/.venv/bin/python -c \
'import mcp; from tools import mcp_tool; assert getattr(mcp_tool, "_MCP_AVAILABLE", False), "Hermes MCP client runtime is unavailable"; assert getattr(mcp_tool, "_MCP_HTTP_AVAILABLE", False), "Hermes MCP Streamable HTTP runtime is unavailable"'
'import mcp; from tools import mcp_tool; assert mcp_tool._ensure_mcp_sdk(), "Hermes MCP client runtime is unavailable"; assert getattr(mcp_tool, "_MCP_AVAILABLE", False), "Hermes MCP client runtime is unavailable"; assert getattr(mcp_tool, "_MCP_HTTP_AVAILABLE", False), "Hermes MCP Streamable HTTP runtime is unavailable"'

RUN chmod -R a+rX /opt/hermes/.venv \
&& test -r /opt/hermes/.venv/pyvenv.cfg \
RUN test -r /opt/hermes/.venv/pyvenv.cfg \
&& su -s /bin/sh sandbox -c '/opt/hermes/.venv/bin/python3 -c "import urllib.request"'

# Reject build-only paths before the base image can be published.
Expand Down
25 changes: 25 additions & 0 deletions agents/hermes/agent-browser-runtime/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion agents/hermes/config/managed-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const MANAGED_IMAGE_HERMES_SUPPORTED_PLATFORMS = [
"google_chat",
] as const;

// Hermes v0.19.0 also packages platform plugins and built-in adapters that are
// Hermes v0.20.6 also packages platform plugins and built-in adapters that are
// not yet supported by NemoClaw's messaging manifests. A neutral managed image
// must explicitly disable the complete installed surface, while keeping this
// list separate from the supported/activatable contract above.
Expand Down
Loading
Loading