Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c56fad1
Add audit fix plan: box new is broken on a clean checkout
Jun 30, 2026
bf6c0fb
fix(P0-1): re-pin Hermes installer SHA256 to current upstream
Jun 30, 2026
762aead
fix(P1-5): decompression-bomb gate now bounds actual extracted bytes
Jun 30, 2026
92cf51c
fix(P0-2): stop verify_required from silently killing provisioning
Jun 30, 2026
85c0413
fix(P1-1): lock hb's gateway check-then-spawn against double-spawn
Jun 30, 2026
ecb1a53
fix(P0-3): preserve failure evidence before rollback destroys it
Jun 30, 2026
a557058
fix(P1-4): pin agent tooling versions instead of always installing la…
Jun 30, 2026
4f334c6
fix(P1-2): supervise the hb-workload loop process
Jun 30, 2026
14b89ff
fix(P1-3): gateway_is_disabled() now checks process state too
Jun 30, 2026
32404eb
fix(P2-1): make assets repair mode able to heal Hermes/Executor
Jun 30, 2026
a912ac2
test(P2-2): add coverage for box ls/rm/stop/start/enter and two gaps
Jun 30, 2026
618b558
ci(P2-3): run make check on every push and PR to main
Jun 30, 2026
c482252
refactor(P2-4): split tests/regressions.sh, extract shared tests/lib.sh
Jun 30, 2026
01a38a5
fix(ci): pin shellcheck to v0.11.0 instead of apt's drifting version
Jun 30, 2026
9eacede
fix(test): give cli-surface.sh's empty-registry ls test the fixture PATH
Jun 30, 2026
87210fb
fix(P3-1): drop personal path default from box.env
Jun 30, 2026
658c7cb
fix(P3-2): harden the three tx9 systemd units
Jun 30, 2026
00b0a9e
fix(P3-3): ship NAS automount template and backup retention
Jun 30, 2026
5a10417
docs(P3-4): make README the single source of truth for duplicated con…
Jun 30, 2026
c436640
fix(review): replace guest/hb's daemon lock with flock, fix real races
Jun 30, 2026
2167b4b
fix(review): Makefile fail-fast loop + curl failure masking
Jun 30, 2026
5391170
fix(review): don't rely on root home's default permissions for agent …
Jun 30, 2026
65ad3b4
fix(review): macOS portability, temp-dir leak, and minor nitpicks
Jun 30, 2026
df2b2c5
fix(review): close cmd_load's failure-evidence gaps, harden transcrip…
Jun 30, 2026
04cc316
fix(review): install_executor's idempotency guard now checks version
Jun 30, 2026
ee8e7d0
docs: note that P1-1's original mkdir-based lock was superseded
Jun 30, 2026
b5583a7
refactor: dedupe MCP handshake between box and guest/hb, note deferre…
Jul 1, 2026
f564a22
fix(review): cmd_load reports which stage failed, matching cmd_new
Jul 1, 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
31 changes: 31 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: check

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
make-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install jq
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends jq
- name: Install shellcheck
# Pinned instead of apt's (often much older) package: older shellcheck
# versions flag `[[ cond ]] && cmd || true` as SC2015, a check that
# current shellcheck deliberately doesn't apply to `[[ ]]` tests. Pin
# to match what local development already runs against.
env:
SHELLCHECK_VERSION: v0.11.0
run: |
curl -fsSL "https://github.com/koalaman/shellcheck/releases/download/${SHELLCHECK_VERSION}/shellcheck-${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" \
| tar -xJ
sudo install "shellcheck-${SHELLCHECK_VERSION}/shellcheck" /usr/local/bin/shellcheck
shellcheck --version
- name: make check
run: make check
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@
# Optional private/custom Hermes Git bundles used for exact-SHA provisioning.
/artifacts/*
!/artifacts/.gitkeep

__pycache__/
32 changes: 29 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
.PHONY: check syntax lint test
.PHONY: check syntax lint test check-hermes-pin

SHELL_FILES := box guest/hb guest/hb-workload guest/profile.sh guest/agent-bash-profile.sh provision/provision.sh ops/tx9-host tests/static.sh tests/lifecycle-smoke.sh tests/hermes-state.sh tests/regressions.sh tests/fixtures/smolvm
# tests/regressions-*.sh is globbed so new split-out regression files don't
# require touching this Makefile.
SHELL_FILES := box guest/hb guest/hb-workload guest/lib-mcp.sh guest/profile.sh guest/agent-bash-profile.sh provision/provision.sh ops/tx9-host ops/tx9-backup-prune tests/lib.sh tests/static.sh tests/lifecycle-smoke.sh tests/hermes-state.sh tests/cli-surface.sh $(wildcard tests/regressions-*.sh) tests/fixtures/smolvm

syntax:
bash -n $(SHELL_FILES)
Expand All @@ -16,6 +18,30 @@ test:
./tests/static.sh
./tests/lifecycle-smoke.sh
./tests/hermes-state.sh
./tests/regressions.sh
./tests/cli-surface.sh
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
for f in tests/regressions-*.sh; do ./"$$f" || exit 1; done

check: syntax lint test

# Not part of `check` — needs network access, so `make check` stays hermetic.
# Run this on its own to catch Hermes installer drift before it surfaces as a
# `box new` failure for a real user.
check-hermes-pin:
@command -v curl >/dev/null || { echo "curl is required for check-hermes-pin" >&2; exit 1; }
@if command -v sha256sum >/dev/null; then hasher="sha256sum"; \
elif command -v shasum >/dev/null; then hasher="shasum -a 256"; \
else echo "sha256sum or shasum (macOS) is required for check-hermes-pin" >&2; exit 1; fi; \
pinned=$$(grep '^HERMES_INSTALLER_SHA256=' box.env | sed -E 's/^[A-Z0-9_]+="([^"]*)"/\1/'); \
tmp=$$(mktemp); \
trap 'rm -f "$$tmp"' EXIT; \
if ! curl -fsSL --connect-timeout 10 --max-time 30 https://hermes-agent.nousresearch.com/install.sh -o "$$tmp"; then \
echo "failed to fetch live Hermes installer" >&2; exit 1; \
fi; \
live=$$($$hasher "$$tmp" | awk '{print $$1}'); \
if [ "$$pinned" = "$$live" ]; then \
echo "HERMES_INSTALLER_SHA256 matches upstream ($$live)"; \
else \
echo "HERMES_INSTALLER_SHA256 in box.env ($$pinned) does NOT match upstream ($$live)" >&2; \
echo "review the new installer before re-pinning, then update box.env" >&2; \
exit 1; \
fi
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,5 +224,6 @@ make check

This requires ShellCheck, Python 3, and jq, and runs Bash syntax checks, static
contract checks, protocol-health regressions, and local archive/lifecycle smoke
tests. It does not create a VM or contact upstream installers. There is
intentionally no CI configuration yet.
tests. It does not create a VM or contact upstream installers. `make check` is
fully hermetic, so `.github/workflows/check.yml` runs it on every push and
pull request to `main`.
166 changes: 90 additions & 76 deletions box
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$ROOT"
# shellcheck source=box.env
source "$ROOT/box.env"
# shellcheck source=guest/lib-mcp.sh
source "$ROOT/guest/lib-mcp.sh"

REG="$ROOT/.boxes"
LOCK_ROOT="$ROOT/.box-locks"
Expand Down Expand Up @@ -217,69 +219,6 @@ _port_free() {
return 0
}

_mcp_initialize() {
local url="$1" token="$2" headers body code session content_type initialized_code initialized_ok=0
headers="$(mktemp)" || return 1
body="$(mktemp)" || { rm -f -- "$headers"; return 1; }
_track_temp "$headers"
_track_temp "$body"
if ! code="$(curl -sS --connect-timeout 2 --max-time 5 -D "$headers" -o "$body" -w '%{http_code}' \
-X POST \
-H "Authorization: Bearer $token" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json,text/event-stream' \
--data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"hermes-box-doctor","version":"1"}}}' \
"$url")"; then
rm -f -- "$headers" "$body"
return 1
fi
session="$(awk 'tolower($1) == "mcp-session-id:" { sub(/^[^:]*:[[:space:]]*/, ""); sub(/\r$/, ""); print; exit }' "$headers")"
content_type="$(awk 'tolower($1) == "content-type:" { sub(/^[^:]*:[[:space:]]*/, ""); sub(/\r$/, ""); print tolower($0); exit }' "$headers")"
if [[ "$code" != 200 || -z "$session" ]] || ! _mcp_response_valid "$body" "$content_type"; then
rm -f -- "$headers" "$body"
[[ -n "$session" ]] && _mcp_delete_session "$url" "$token" "$session"
return 1
fi
if initialized_code="$(curl -sS --connect-timeout 2 --max-time 5 -o /dev/null -w '%{http_code}' \
-X POST \
-H "Authorization: Bearer $token" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json,text/event-stream' \
-H "Mcp-Session-Id: $session" \
--data '{"jsonrpc":"2.0","method":"notifications/initialized"}' "$url")" &&
[[ "$initialized_code" == 202 ]]; then
initialized_ok=1
fi
rm -f -- "$headers" "$body"
_mcp_delete_session "$url" "$token" "$session"
[[ "$initialized_ok" == 1 ]]
}

_mcp_delete_session() {
local url="$1" token="$2" session="$3"
curl -sS --connect-timeout 2 --max-time 5 -o /dev/null -X DELETE \
-H "Authorization: Bearer $token" \
-H 'Accept: application/json,text/event-stream' \
-H "Mcp-Session-Id: $session" "$url" >/dev/null 2>&1 || true
}

_mcp_response_valid() {
local body="$1" content_type="$2" filter
filter='type == "object" and .jsonrpc == "2.0" and .id == 1 and
has("result") and (has("error") | not) and
(.result | type == "object") and
.result.protocolVersion == "2025-03-26" and
(.result.serverInfo | type == "object") and
(.result.serverInfo.name | type == "string" and length > 0) and
(.result.serverInfo.version | type == "string") and
(.result.capabilities | type == "object")'
case "$content_type" in
application/json*) jq -e "$filter" "$body" >/dev/null 2>&1 ;;
text/event-stream*) sed -n 's/^data:[[:space:]]*//p' "$body" | jq -e "$filter" >/dev/null 2>&1 ;;
*) return 1 ;;
esac
}

_wait_mcp_ready() {
local url="$1" token="$2" attempt
for ((attempt = 0; attempt < 30; attempt++)); do
Expand Down Expand Up @@ -543,8 +482,13 @@ _create_base() {
[[ -z "${BOX_STORAGE_GIB:-}" ]] || resources+=(--storage "$BOX_STORAGE_GIB")
wl_port="${port:-4788}"
_defer_signals
# No `exec` into hb-workload: if the loop process itself dies (OOM-killed, a
# bug not guarded by its `set -u`), nothing else in the VM restarts it, so
# this outer loop is the supervisor. hb-workload's own internals (gateway,
# socat bridges) are reconciled every 20s once it's running again.
if smolvm machine create --net "${opts[@]}" "${resources[@]}" --name "$n" --image "$BASE_IMAGE" \
-- bash -c "while [ ! -x /opt/hermes-box/bin/hb-workload ]; do sleep 2; done; exec runuser -u agent -- env HOME=/data/home/agent /opt/hermes-box/bin/hb-workload ${wl_port} ${api_guest_port:-0}"; then
-- bash -c "while [ ! -x /opt/hermes-box/bin/hb-workload ]; do sleep 2; done
while true; do runuser -u agent -- env HOME=/data/home/agent /opt/hermes-box/bin/hb-workload ${wl_port} ${api_guest_port:-0}; sleep 2; done"; then
create_status=0
else
create_status=$?
Expand All @@ -571,7 +515,7 @@ _create_base() {
}

_provision_into() {
local n="$1" mode="${2:-full}" contexts=(provision guest box.env)
local n="$1" mode="${2:-full}" log_file="${3:-}" contexts=(provision guest box.env) cmd
if [[ "$mode" == full ]]; then
_validate_bundle
[[ -z "${HERMES_GIT_BUNDLE:-}" ]] || contexts+=("$HERMES_GIT_BUNDLE")
Expand All @@ -586,11 +530,17 @@ _provision_into() {
else
say "Provisioning node/uv, claude, codex, hermes, executor (a few minutes)…"
fi
smolvm machine exec --name "$n" -- bash -lc "
set -e
cmd="set -e
rm -rf /tmp/ctx && mkdir -p /tmp/ctx
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
tar xzf /tmp/ctx.tgz -C /tmp/ctx
bash /tmp/ctx/provision/provision.sh '$mode'"
# Optionally tee the transcript to a host-side file so a caller can persist
# it as failure evidence before rollback deletes the only other copy.
if [[ -n "$log_file" ]]; then
smolvm machine exec --name "$n" -- bash -lc "$cmd" 2>&1 | tee "$log_file"
else
smolvm machine exec --name "$n" -- bash -lc "$cmd"
fi
}

_prepare_runtime() {
Expand All @@ -600,6 +550,25 @@ _prepare_runtime() {
_guest_hb "$n" doctor
}

# Best-effort: a failure saving the transcript (full disk, read-only checkout)
# must never itself abort the caller's failure-handling path under set -e —
# that would skip keep-on-failure's _clear_created and let the EXIT trap roll
# the box back anyway, defeating the one thing the operator needed most.
_save_failed_transcript() {
local n="$1" log_file="$2" failed_log
[[ -s "$log_file" ]] || return 0
if ! mkdir -p backups/.failed; then
warn "could not create backups/.failed; provisioning transcript was not saved"
return 0
fi
failed_log="backups/.failed/${n}-$(date -u +%Y%m%dT%H%M%SZ).log"
if cp "$log_file" "$failed_log"; then
warn "provisioning transcript saved to $failed_log"
else
warn "could not save provisioning transcript to $failed_log"
fi
}

_rollback_created() {
local n="$1" registry_lock="$LOCK_ROOT/registry.lock" owns_lock=0
warn "rolling back incomplete box '$n'"
Expand Down Expand Up @@ -633,13 +602,30 @@ _rollback_created() {
}

cmd_new() {
local n="${1:?usage: box new <name>}"
local n="${1:?usage: box new <name> [--keep-on-failure]}" keep_on_failure=0 stage="" log_file
[[ "${2:-}" != --keep-on-failure && "${BOX_KEEP_ON_FAILURE:-0}" != 1 ]] || keep_on_failure=1
_validate_name "$n"
_host_preflight provision
_acquire_lock "$n"
say "Creating instance '$n' from $BASE_IMAGE"
if ! _create_base "$n" || ! _provision_into "$n" full || ! _prepare_runtime "$n"; then
die "box creation failed"
log_file="$(mktemp)"
_track_temp "$log_file"
if ! _create_base "$n"; then
stage="creating the base VM"
elif ! _provision_into "$n" full "$log_file"; then
stage="provisioning"
elif ! _prepare_runtime "$n"; then
stage="preparing the runtime (gateway start, MCP wiring, doctor)"
fi
if [[ -n "$stage" ]]; then
_save_failed_transcript "$n" "$log_file"
if [[ "$keep_on_failure" == 1 ]]; then
_clear_created "$n"
warn "leaving '$n' in place for inspection (--keep-on-failure / BOX_KEEP_ON_FAILURE=1)"
warn "inspect with: smolvm machine exec --name '$n' -- bash"
warn "remove when done with: ./box rm '$n' --force"
fi
die "box creation failed while $stage"
fi
_clear_created "$n"
local port
Expand Down Expand Up @@ -705,7 +691,9 @@ cmd_save() {
}

cmd_load() {
local file="${1:?usage: box load <archive.tar.gz.gpg> [name]}" n="${2:-restored}" raw guest_tmp
local file="${1:?usage: box load <archive.tar.gz.gpg> [name] [--keep-on-failure]}" n="${2:-restored}" raw guest_tmp

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium box:755

cmd_load assigns n="${2:-restored}" before checking for --keep-on-failure, so the invocation ./box load <file> --keep-on-failure parses the flag as the box name. _validate_name then rejects --keep-on-failure and the restore never starts, even though the usage string advertises the flag as usable without an explicit name. Consider detecting --keep-on-failure in $2 before assigning it to n, or parsing positional and flag arguments separately.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @box around line 755:

`cmd_load` assigns `n="${2:-restored}"` before checking for `--keep-on-failure`, so the invocation `./box load <file> --keep-on-failure` parses the flag as the box name. `_validate_name` then rejects `--keep-on-failure` and the restore never starts, even though the usage string advertises the flag as usable without an explicit name. Consider detecting `--keep-on-failure` in `$2` before assigning it to `n`, or parsing positional and flag arguments separately.

local keep_on_failure=0 log_file stage=""
[[ "${3:-}" != --keep-on-failure && "${BOX_KEEP_ON_FAILURE:-0}" != 1 ]] || keep_on_failure=1
[[ -f "$file" ]] || die "no such archive: $file"
_validate_name "$n"
_host_preflight full
Expand All @@ -717,8 +705,21 @@ cmd_load() {
_validate_archive "$raw" || die "archive is invalid or unsafe"

say "Creating and provisioning fresh instance '$n'"
if ! _create_base "$n" || ! _provision_into "$n" full; then
die "destination provisioning failed"
log_file="$(mktemp)"
_track_temp "$log_file"
if ! _create_base "$n"; then
stage="creating the base VM"
elif ! _provision_into "$n" full "$log_file"; then
stage="provisioning"
fi
if [[ -n "$stage" ]]; then
_save_failed_transcript "$n" "$log_file"
if [[ "$keep_on_failure" == 1 ]]; then
_clear_created "$n"
warn "leaving '$n' in place for inspection (--keep-on-failure / BOX_KEEP_ON_FAILURE=1)"
warn "remove when done with: ./box rm '$n' --force"
fi
die "box load failed while $stage"
fi

guest_tmp="/root/hb-restore-$(date +%Y%m%d-%H%M%S)-$$-${RANDOM}.tgz"
Expand Down Expand Up @@ -764,8 +765,15 @@ cmd_load() {
trap '\''if [[ "$restore_failed" == 1 ]]; then find /data -mindepth 1 -maxdepth 1 ! -path "$rollback" -exec rm -rf -- {} +; mv "$rollback"/* /data/ 2>/dev/null || true; fi; rm -rf "$stage" "$rollback"; rm -f -- "$archive"'\'' EXIT HUP INT TERM
mv "$stage"/* /data/
restore_failed=0' bash "$guest_tmp" ||
! _provision_into "$n" assets || ! _guest_hb "$n" gateway-disable ||
! _provision_into "$n" assets "$log_file" || ! _guest_hb "$n" gateway-disable ||
! _guest_hb "$n" verify-state >/dev/null || ! _resume_box "$n" || ! _prepare_runtime "$n"; then
_save_failed_transcript "$n" "$log_file"
if [[ "$keep_on_failure" == 1 ]]; then
_clear_created "$n"
warn "leaving '$n' in place for inspection (--keep-on-failure / BOX_KEEP_ON_FAILURE=1)"
warn "remove when done with: ./box rm '$n' --force"
die "restore failed; left incomplete destination '$n' for inspection"
fi
die "restore failed; attempting cleanup of the incomplete destination"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
fi
_untrack_guest_temp "$n" "$guest_tmp"
Expand Down Expand Up @@ -1051,21 +1059,27 @@ usage() {
cat <<'EOF'
hermes-box-lite — isolated, portable agent boxes (claude / codex / hermes / executor)

./box new <name> Create, provision, and health-check a fresh box
./box new <name> [--keep-on-failure]
Create, provision, and health-check a fresh box
./box enter <name> Log in as agent and attach tmux 'main'
./box ls [--all] List managed boxes (or every smolvm VM)
./box open <name> Open the authenticated Executor web UI
./box doctor <name> Read-only health check; never starts or resumes a box
./box repair <name> Re-run provisioning and repair managed guest assets
./box stop|start <name> Stop or start an instance
./box save <name> [out] Quiesce, validate, encrypt, and verify /data
./box load <file> [name] Validate first, then build and restore a fresh box
./box load <file> [name] [--keep-on-failure]
Validate first, then build and restore a fresh box
./box import-hermes <name> <hermes-backup.zip> [--external NAME]... [--map OLD=NEW]...
Transactionally import native Hermes state; gateway stays disabled
./box extract <file> [dir] Validate and extract a backup without a VM
./box rm <name> [--force] Delete after confirmation (or explicit --force)

Set BOX_PASSPHRASE to skip the GPG prompt. Configuration: box.env
Set BOX_KEEP_ON_FAILURE=1 (or pass --keep-on-failure to `new`/`load`) to
skip rollback on a failed `new`/`load` and leave the VM up for inspection.
A failed `new`/`load` also saves its provisioning transcript under
backups/.failed/ before any rollback runs.
EOF
}

Expand Down
23 changes: 19 additions & 4 deletions box.env
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ BOX_STORAGE_GIB=""
# Tool major line selected during provisioning.
NODE_MAJOR="22" # NodeSource major line; claude-code, codex, executor run on Node

# Pin exact npm package versions so `box new`/`box repair` are reproducible
# instead of always installing whatever is currently `latest`. Empty (the
# default) keeps the old latest-on-install behavior.
CLAUDE_CODE_VERSION="" # e.g. "2.1.197" — see `npm view @anthropic-ai/claude-code versions`
CODEX_VERSION="" # e.g. "0.142.4" — see `npm view @openai/codex versions`
EXECUTOR_VERSION="" # e.g. "1.5.26" — the github.com/RhysSullivan/executor npm
# package; "executor" is a generic name, so double-check
# `npm view executor repository.url` if pinning a version

# --- Hermes Agent (Nous Research) -------------------------------------------
# Installed headless via the official script. Code -> /usr/local/lib (root-mode
# FHS layout); state -> $HERMES_HOME on /data.
Expand All @@ -22,8 +31,10 @@ HERMES_INSTALL_ARGS="--skip-browser" # headless box: skip Playwright/Chromium
HERMES_REPO_URL="https://github.com/NousResearch/hermes-agent.git"
HERMES_GIT_SHA="244a6f2ceb7f58c16b3cb2186584c39524e37874"
# SHA-256 of https://hermes-agent.nousresearch.com/install.sh, verified before
# the installer is ever executed as root. Update intentionally with upstream.
HERMES_INSTALLER_SHA256="58f859ab038829a8538d001247db46680bac402a3ed746fdd81c51401bda4d71"
# the installer is ever executed as root. Update intentionally with upstream;
# last verified against upstream 2026-06-30. Run `make check-hermes-pin` to
# check this pin against the live installer without committing a new one.
HERMES_INSTALLER_SHA256="a93c65b01ea392e179cf872e182bd01a2b65c0c15f17833e9f9569033ef10e07"
# Optional repository-relative, self-contained Git bundle. Keep private/custom
# bundles under artifacts/ (ignored by Git). The commit and all of its required
# objects must be present; incremental bundles with external prerequisites fail.
Expand All @@ -32,8 +43,12 @@ HERMES_API_PORT="8642"
EXPOSE_HERMES_API=0
HERMES_API_PORT_LO=8650
HERMES_API_PORT_HI=8850
# Applied only to active imported config/env/cron/hook/script files.
HERMES_IMPORT_PATH_MAP="/Users/davis=/data/home/agent"
# Applied only to active imported config/env/cron/hook/script files. Empty by
# default — this is operator/machine-specific (e.g. your old macOS username),
# not something to commit. Set it here for your own deployment, or pass
# --map OLD=NEW per invocation (see docs/nexus-operations.md's migration
# runbook for a worked example); import-hermes applies both if both are set.
HERMES_IMPORT_PATH_MAP=""

# --- Executor (github.com/RhysSullivan/executor) ----------------------------
# npm global; daemon + web UI + MCP. Data lives under $HOME (=/data) via XDG.
Expand Down
Loading
Loading