sha256:6f7cdf14119544d14fc87c98bf7f0768dd4f9d41a92e161426975375a1220d31
Last pushed
15 days by docker
Type
Sandbox Kit
Manifest digest
sha256:6f7cdf14119544d14fc87c98bf7f0768dd4f9d41a92e161426975375a1220d31
schemaVersion: "2"
kind: sandbox
name: hermes-hsp-kit
displayName: Hermes Agent (HSP Steering)
description: HSP-capable Hermes Agent kit -- the self-improving AI agent by Nous Research, pinned to commit 63279301bcbdc185c1b07b98a9312eb0c862f26d (v0.21.0 / tag v2026.8.31), plus this repo's `hermes-hsp` in-process Python plugin and a plugin-spawned Node shim, wired up so the session declares Harness Steering Protocol (hsp/1) support. The base image builds directly from the Docker-owned docker/sandbox-templates:shell-docker template (see hermes/kit/Dockerfile), reproducing the same Hermes install steps simonferquel-clanker/hermes-agent-mcp-kit's own base image uses (pinned to a commit here, where that kit installs unpinned `main`) -- no dependency on any externally-hosted derivative image. Also self-registers the sandbox's hosted MCP gateway (when reserved) into Hermes's own ~/.hermes/config.yaml, same as the grounding kit. Built to test/demo the Agentic Platform's custom-kit-only HSP capability (APT-1382, phase 3 -- feasibility study artifact e780227f).
sandbox:
image: docker.io/docker/hsp:hermes-base-image-latest
entrypoint:
- /home/agent/.local/bin/hermes
agentInstructions:
filename: AGENTS.md
permissions:
network:
allow:
- github.com
- raw.githubusercontent.com
- objects.githubusercontent.com
- nodejs.org
- registry.npmjs.org
- pypi.org
- files.pythonhosted.org
- astral.sh
- openrouter.ai
- '*.openrouter.ai'
- api.openai.com
- api.anthropic.com
- portal.nousresearch.com
- duckduckgo.com
- archive.ubuntu.com
- security.ubuntu.com
- ports.ubuntu.com
- download.docker.com
credentials:
- service: anthropic
apiKey:
name: ANTHROPIC_API_KEY
proxyManaged: true
inject:
- domain: api.anthropic.com
header: x-api-key
format: '%s'
- service: openai
apiKey:
name: OPENAI_API_KEY
proxyManaged: true
inject:
- domain: api.openai.com
header: Authorization
format: Bearer %s
- service: openrouter
apiKey:
name: OPENROUTER_API_KEY
proxyManaged: true
inject:
- domain: openrouter.ai
header: Authorization
format: Bearer %s
environment:
variables:
HERMES_HOME: /home/agent/.hermes
REQUESTS_CA_BUNDLE: /etc/ssl/certs/ca-certificates.crt
SSL_CERT_FILE: /etc/ssl/certs/ca-certificates.crt
UV_NATIVE_TLS: "true"
UV_SYSTEM_CERTS: "true"
setup:
startup:
- command:
- sh
- -c
- |
set -e
mkdir -p /usr/local/share/ca-certificates
if [ -f /etc/ssl/certs/credentials-proxy-ca.crt ]; then
cp /etc/ssl/certs/credentials-proxy-ca.crt \
/usr/local/share/ca-certificates/credentials-proxy-ca.crt
update-ca-certificates
fi
user: "0"
description: Merge the sandbox proxy CA into the system CA bundle so Python/httpx TLS verification succeeds through the credential-injecting MITM proxy
- command:
- sh
- -c
- |
set -e
HERMES_LAUNCHER="$(command -v hermes 2>/dev/null || true)"
[ -n "$HERMES_LAUNCHER" ] || HERMES_LAUNCHER="$HOME/.local/bin/hermes"
# Defensive only: install now bakes hermes into the image, so the
# launcher should always exist by this point.
[ -x "$HERMES_LAUNCHER" ] || exit 0
# $HERMES_LAUNCHER is the bash shim hermes-agent's own installer
# generates: `exec "<venv>/bin/python" "<install_dir>/hermes" "$@"`.
# Pull out that interpreter so this reuses Hermes's own Python +
# ruamel.yaml rather than depending on a system python3.
PYTHON_BIN="$(awk -F'"' '/^exec /{print $2; exit}' "$HERMES_LAUNCHER" 2>/dev/null || true)"
[ -n "$PYTHON_BIN" ] && [ -x "$PYTHON_BIN" ] || exit 0
"$PYTHON_BIN" -c "import ruamel.yaml" >/dev/null 2>&1 || exit 0
CONFIG_PATH="${HERMES_HOME:-$HOME/.hermes}/config.yaml"
GATEWAY_URL="$MCP_GATEWAY_URL" GATEWAY_TOKEN_NAME="${MCP_SENTINEL_TOKEN_NAME:-mcp-gateway}" \
MODEL_PROVIDER="anthropic" MODEL_DEFAULT="claude-opus-5" \
CONFIG_PATH="$CONFIG_PATH" "$PYTHON_BIN" <<'PYEOF'
import os
from ruamel.yaml import YAML
yaml = YAML()
yaml.preserve_quotes = True
yaml.indent(mapping=2, sequence=4, offset=2)
yaml.width = 4096
config_path = os.environ["CONFIG_PATH"]
gateway_url = os.environ.get("GATEWAY_URL", "")
token_name = os.environ.get("GATEWAY_TOKEN_NAME", "mcp-gateway")
model_provider = os.environ["MODEL_PROVIDER"]
model_default = os.environ["MODEL_DEFAULT"]
config = {}
if os.path.exists(config_path):
with open(config_path, "r") as f:
loaded = yaml.load(f)
if isinstance(loaded, dict):
config = loaded
# Baked-in favourite: always set provider/model so the kit's
# preferred inference endpoint takes effect on every start.
model = config.get("model")
if not isinstance(model, dict):
model = {}
config["model"] = model
model["provider"] = model_provider
model["default"] = model_default
# Gateway registration -- only when a gateway is reserved.
if gateway_url:
servers = config.get("mcp_servers")
if not isinstance(servers, dict):
servers = {}
config["mcp_servers"] = servers
servers["mcp-gateway"] = {
"url": gateway_url,
"headers": {"Authorization": f"Bearer {token_name}"},
}
# HSP: list-merge (never overwrite) -- preserves any other plugin
# already present in plugins.enabled.
plugins = config.get("plugins")
if not isinstance(plugins, dict):
plugins = {}
config["plugins"] = plugins
enabled = plugins.get("enabled")
if not isinstance(enabled, list):
enabled = []
if "hermes-hsp" not in enabled:
enabled.append("hermes-hsp")
plugins["enabled"] = enabled
os.makedirs(os.path.dirname(config_path), exist_ok=True)
tmp_path = config_path + ".tmp"
with open(tmp_path, "w") as f:
yaml.dump(config, f)
os.replace(tmp_path, config_path)
PYEOF
user: "1000"
description: Bake in direct Anthropic (claude-opus-5) as the default model/provider, register the MCP gateway when one is reserved, and enable the hermes-hsp HSP plugin, into ~/.hermes/config.yaml