Sign inSign up

docker/hsp:hermes-kit-latest

Manifest digest

sha256:6f7cdf14119544d14fc87c98bf7f0768dd4f9d41a92e161426975375a1220d31

Last pushed

15 days by docker

Type

Sandbox Kit

Manifest digest

sha256:6f7cdf14119544d14fc87c98bf7f0768dd4f9d41a92e161426975375a1220d31

yaml
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