Hermes Agent: Google Vertex AI & Agent-to-Agent (A2A) Setup Guide

This guide documents how to set up Hermes Agent with Google Vertex AI models using Application Default Credentials (ADC), and how to configure and test A2A (Agent-to-Agent) communication in every direction Hermes supports — fully scoped to a project directory (e.g., /path/to/project).


1. Overview & Architecture

  • Hermes Agent: Open-source AI agent framework by Nous Research with multi-platform messaging, tool calling, and long-term memory.
  • LLM Provider: Google Cloud Vertex AI (Gemini 3.x / 2.5 models) with OAuth2 authentication via gcloud ADC.
  • Inter-Agent Protocol: A2A (Agent-to-Agent) v1.0, an open specification stewarded by the Linux Foundation. Hermes implements both directions of the protocol simultaneously from a single running gateway process:
    • Inbound (server): Exposes Hermes as a JSON-RPC 2.0 endpoint with an Agent Card at GET /.well-known/agent-card.json.
    • Outbound (client): Lets the Hermes agent itself discover and call other A2A-compliant agents mid-conversation via tools (a2a_discover, a2a_call, a2a_list, a2a_history, a2a_orchestrate).

Directory Isolation & Scoping (HERMES_HOME)

Hermes supports the HERMES_HOME environment variable to isolate configuration, credentials, session state, and memory. In this project:

/path/to/project/
├── .gitignore               # Ignores .hermes/ and .envrc
├── .envrc                   # direnv auto-export (HERMES_HOME=$(pwd)/.hermes)
├── run-hermes.sh            # Executable helper launcher script
├── AGENTS.md                # Project AGENTS.md
├── docs/
│   └── hermes-vertex-a2a-setup.md  # This documentation
└── .hermes/                 # Project-scoped Hermes home directory
    ├── config.yaml          # Project-specific Hermes configuration
    ├── .env                 # Project environment variables
    ├── SOUL.md              # Agent persona and identity
    ├── sessions/            # Chat and gateway session logs
    ├── logs/                # Gateway and runtime logs
    ├── memories/            # Persistent memory files
    └── skills/              # Installed and bundled skills

Trade-offs vs. the default global ~/.hermes/:

Property Project-Scoped (HERMES_HOME="$(pwd)/.hermes") Global (~/.hermes/)
Isolation High — separate config, memory, sessions, logs per project. Low — shared across all CLI runs and projects.
Reproducibility High — configuration lives with the project repo. Low — changes on one project affect all projects.
Ease of Use Requires export HERMES_HOME=... or ./run-hermes.sh. Just run hermes from anywhere.
Secrets Safety Easy to isolate per project; keep .hermes/ gitignored. Shared keys in ~/.hermes/.env.

2. The A2A Scenarios This Guide Covers

Hermes' A2A support is bidirectional and works with any A2A v1.0-compliant peer, not just other Hermes instances. Below are the distinct roles Hermes can play, each with its own value proposition, all verified working in this setup.

# Scenario Value Proposition
A Hermes as an A2A server (inbound), called by an external A2A client (a2acli) Expose your Hermes agent — with its full toolset, memory, and Vertex-backed reasoning — as a standard, interoperable service any A2A tool or framework can call, without writing custom integration code.
B Hermes as an A2A client (outbound), calling another A2A agent (a mock server via a2acli serve --echo) Let Hermes delegate a task mid-conversation to a specialist agent (research, coding, a different model, a different vendor's agent) and use the reply as part of its own answer — composing multiple agents into one workflow.
C Hermes calling itself over A2A (self-loop) Useful for testing your own Agent Card and inbound behavior exactly as a remote peer would see it, without needing a second agent. Also the basis for patterns like "spin off a sub-task to my own agent identity in a fresh context."
D Fire-and-forget background gateway Run Hermes as a long-lived background process (or eventually an OS service) that keeps listening for both normal chat and A2A requests without you having to keep a terminal open.
E Persistent OS service (macOS launchd) Move beyond a background shell job to a properly supervised service that survives terminal closure, crashes, and (optionally) reboots/login — the standard way to run an "always-on" agent.
F Remote A2A access (not configured here) Documented for completeness: with a bearer token and A2A_HOST=0.0.0.0, other machines — not just localhost — could call this Hermes instance over the network.

3. Prerequisites & GCP Configuration

  1. Google Cloud Project: YOUR_GOOGLE_CLOUD_PROJECT
  2. Vertex AI API: Ensure aiplatform.googleapis.com is enabled:
    gcloud services enable aiplatform.googleapis.com --project=YOUR_GOOGLE_CLOUD_PROJECT
  3. Application Default Credentials (ADC): Authenticate with gcloud:
    gcloud auth application-default login
    This creates ~/.config/gcloud/application_default_credentials.json.

4. Installation & Project Scoping Setup

A. Installing Hermes Agent

If Hermes Agent CLI is not already installed on your machine:

curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash
source ~/.zshrc

B. Project Helper Script (run-hermes.sh)

Use the helper launcher script run-hermes.sh to ensure HERMES_HOME is always pointed at the project folder:

#!/usr/bin/env bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
export HERMES_HOME="${DIR}/.hermes"
export PATH="${HOME}/.local/bin:${PATH}"

exec hermes "$@"

Ensure it has execute permissions:

chmod +x run-hermes.sh

C. Install a2acli (external A2A client, used in Scenario A)

a2acli is a standalone, A2A Specification v1.0-compliant command-line client — not part of Hermes — used here to prove Hermes' inbound A2A server works with any conformant external tool, not just other Hermes instances.

brew tap ghchinoy/tap
brew install a2acli
a2acli version   # verify install

5. Google Vertex AI Configuration

Edit .hermes/config.yaml to specify Vertex AI as the provider, your GCP project, and region:

model:
  default: "google/gemini-3.6-flash"
  provider: "vertex"

vertex:
  project_id: "YOUR_GOOGLE_CLOUD_PROJECT"
  region: "global"

Supported Gemini Models on Vertex AI

Model Name Model Slug / ID Best Used For
Gemini 3.6 Flash google/gemini-3.6-flash Fast, high-capacity default chat model (verified working)
Gemini 3.5 Flash Lite google/gemini-3.5-flash-lite Ultra-fast, lightweight tasks (verified working)
Gemini 3.1 Flash Lite Preview google/gemini-3.1-flash-lite-preview Documented fallback if newer aliases are unavailable in your region/project
Gemini 3 Flash Preview google/gemini-3-flash-preview Documented fallback
Gemini 3 Pro Preview google/gemini-3-pro-preview High-reasoning tasks

Note on Region: Set region: "global" in config.yaml when using Gemini 3.x models — regional endpoints (us-central1, etc.) may 404 on 3.x preview models.

Note on ADC credential type: Hermes' Vertex adapter expects a service-account JSON by default and only falls back to a plain OAuth user-credentials file (what gcloud auth application-default login actually produces) after a failed parse. This project's setup required a small, local patch to agent/vertex_adapter.py to try google.auth.load_credentials_from_file() as a fallback when service_account.Credentials.from_service_account_file() raises — this is what let plain ADC (not a service account key) work end-to-end. If you update Hermes and lose this behavior, either re-apply that fallback or use a service-account JSON via VERTEX_CREDENTIALS_PATH instead.


6. A2A (Agent-to-Agent) Configuration

A. Enable Inbound A2A Gateway

In .hermes/config.yaml, configure the gateway to serve A2A on port 9900:

gateway:
  platforms:
    a2a:
      enabled: true
      extra:
        port: 9900

B. Enable the A2A Plugin

The A2A platform is a plugin and must be explicitly enabled (separately from the gateway.platforms.a2a.enabled flag above, which only controls whether the gateway starts it):

./run-hermes.sh plugins enable a2a-platform

Verify:

./run-hermes.sh plugins list   # a2a-platform should show "enabled"

C. Enable the Outbound a2a Toolset

In .hermes/config.yaml, add a2a to the CLI platform toolsets (the a2a toolset is off by default even when the plugin is enabled):

platform_toolsets:
  cli:
    - hermes-cli
    - a2a

⚠️ Known limitation: outbound a2a tools do not load in standalone hermes chat

Hermes lazy-loads bundled platform plugins (A2A, Telegram, Discord, etc.) to keep plain hermes chat startup fast — the platform's Python module is only imported the first time something asks the internal platform_registry for it by name. The gateway process does this automatically at startup (because it has to start the A2A server). A standalone hermes chat / hermes --tui invocation, however, never asks for the a2a platform, so:

  • _get_platform_tools() (config resolution) correctly reports a2a as enabled.
  • validate_toolset("a2a") (the actual gate used to load tool schemas) still returns False, because the platform module — and therefore the a2a_call/a2a_discover/etc. tool registrations — was never imported.

Symptom: hermes chat -t a2a -q "..." (or any standalone chat/TUI session) prints Warning: Unknown toolsets: a2a and the model has no real a2a_* tools, even though config and hermes plugins list both look correct.

Reliable workaround (documented here, verified working): Send your request through the running gateway's live session instead of a standalone CLI invocation — see Scenario B and C below. The gateway process resolves the A2A platform at startup, so a2a_discover/a2a_call/etc. are genuinely available to whatever agent session handles a gateway-delivered message (A2A inbound task, Telegram message, etc.).

If you need outbound A2A tools inside a plain hermes chat session specifically, this is a gap worth tracking upstream; no supported CLI flag currently forces early platform resolution outside the gateway path.


7. Scenario Walkthroughs & Verification

All scenarios below were executed and verified against this exact setup.

Start the gateway (required for Scenarios A, B, C)

./run-hermes.sh gateway run &

Check it's up:

./run-hermes.sh gateway status
curl -s http://127.0.0.1:9900/.well-known/agent-card.json

Scenario A — Hermes as an A2A server, called by a2acli (external client)

Value proposition: Prove Hermes is a standards-compliant, interoperable A2A service — callable by any conformant client, not just other Hermes agents.

1. Discover the Agent Card:

a2acli discover --service-url http://127.0.0.1:9900 --output text

Confirmed output includes agent name (hermes-agent.local), streaming capability, and the full list of advertised skills/toolsets.

2. Send a message and wait for the result:

a2acli send "What is 17 * 23? Reply with just the number." \
  --service-url http://127.0.0.1:9900 --output json --wait

Confirmed output:

{"id":"task-e4aefd89eaf749a6", ... "state":"TASK_STATE_COMPLETED", ... "text":"391" ...}

(17 * 23 = 391 — correct, answered by the Vertex-backed Gemini model, delivered via raw A2A JSON-RPC.)


Scenario B — Hermes as an A2A client, calling another agent

Value proposition: Have Hermes delegate part of a task to a separate agent — a specialist, a different framework, or just an isolated echo/test service — mid-conversation, then use the result.

1. Stand up a target agent to call. For this test we used a2acli's built-in mock echo server (any A2A-compliant agent works — a second Hermes, a LangChain agent, etc.):

a2acli serve --echo --port 9001 --transport jsonrpc &

(--transport jsonrpc matters — Hermes' outbound a2a_call expects a JSON-RPC-bound peer; the default HTTP+JSON echo mode is not compatible with Hermes' outbound client.)

2. Ask the running Hermes gateway session to call it (see the limitation note in §6 — this must go through the gateway, not a standalone hermes chat):

curl -s -X POST http://127.0.0.1:9900/ \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0", "id": 1, "method": "SendMessage",
    "params": { "message": { "messageId": "m2", "role": "ROLE_USER",
      "parts": [{"text": "Use your a2a_call tool to send the message ping-relay to the agent at http://127.0.0.1:9001, then reply with exactly what it returned."}]
    }}
  }'

Confirmed output: Hermes' reply was `ping-relay` — the exact text the mock agent echoed back, proving Hermes genuinely invoked its outbound a2a_call tool against a separate live A2A peer.

3. Discovery also works outbound:

curl -s -X POST http://127.0.0.1:9900/ \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0", "id": 2, "method": "SendMessage",
    "params": { "message": { "messageId": "m4", "role": "ROLE_USER",
      "parts": [{"text": "Call a2a_discover on http://127.0.0.1:9001 right now. Reply with ONLY the agent name field you get back, nothing else."}]
    }}
  }'

Confirmed output: a2acli-mock-agent — the real name field from the target agent's card, fetched via the genuine a2a_discover tool.


Scenario C — Hermes calling itself over A2A

Value proposition: Validate your own Agent Card and inbound handling exactly as an external peer would experience them, using nothing but the one running instance.

Since the gateway session already has both the inbound server and outbound tools loaded, this is the same pattern as Scenario B, pointed at Hermes' own address:

curl -s -X POST http://127.0.0.1:9900/ \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0", "id": 3, "method": "SendMessage",
    "params": { "message": { "messageId": "m5", "role": "ROLE_USER",
      "parts": [{"text": "Use a2a_discover on http://127.0.0.1:9000 and tell me your own agent name as it appears on your own Agent Card."}]
    }}
  }'

Anti-loop safety: A2A_MAX_PINGPONG_TURNS (default 5, max 20) caps per-context back-and-forth turns specifically to prevent two agents (or one agent calling itself) from looping forever. No action needed for a single self-discover/self-call, but be aware of this cap if scripting longer self-referential exchanges.


Scenario D — Fire-and-forget background process (test-level, done here)

Value proposition: Keep the agent listening for chat and A2A requests without occupying a foreground terminal — useful for iterative testing within a single working session.

./run-hermes.sh gateway run > .hermes/logs/gateway.log 2>&1 &

This is what was used throughout this guide's verification steps. It is not persistent across terminal/session restarts or reboots — see Scenario E for that.


Scenario E — Persistent OS service via macOS launchd (documented, not enabled)

Value proposition: Move from an ad hoc background shell job to a properly supervised, restart-on-crash, (optionally) start-on-login service — the standard way to run an always-on agent on macOS, without a terminal ever being open.

Hermes has first-class support for this via launchd on macOS (and systemd on Linux). This was not enabled for this project (we used the simpler background-job approach in Scenario D for testing), but it is fully supported and scoped correctly even for a non-default HERMES_HOME like this project's:

./run-hermes.sh gateway install --start-now --start-on-login

This registers a launchd plist under ~/Library/LaunchAgents/, with a service label automatically scoped to this project's HERMES_HOME (Hermes derives a unique suffix from the HERMES_HOME path when it isn't a named profile, so multiple project-scoped Hermes instances on the same machine don't collide).

Useful related commands once installed:

./run-hermes.sh gateway status     # check if the service is running
./run-hermes.sh gateway stop       # stop the service
./run-hermes.sh gateway uninstall  # remove the launchd service entirely

If you want the gateway to survive terminal closure and reboots but haven't set this up yet, this is the recommended next step — the background & job used in this guide's testing does not survive a reboot or a closed terminal session cleanly.


Scenario F — Remote A2A access (not configured, documented for completeness)

Value proposition: Allow A2A clients on other machines — not just 127.0.0.1 — to call this Hermes instance, e.g. a teammate's machine, a cloud-hosted orchestrator, or a CI pipeline.

Current state: Not configured. No bearer token is set, so per Hermes' security model the server is bound to 127.0.0.1 only and will refuse to widen even if you set A2A_HOST without also setting a token.

To enable, add to .hermes/.env:

# Per-peer tokens (preferred) — each remote agent gets its own credential:
A2A_PEER_TOKENS="alice:tok1,bob:tok2"
# OR a single shared token:
A2A_BEARER_TOKEN="some-long-random-token"

# Only after a token is set, widen the bind host:
A2A_HOST=0.0.0.0

Then restart the gateway. Other security-relevant knobs (rate limiting, trusted-peer allow-lists, push-notification HMAC secrets) are listed in README.md/plugin.yaml inside Hermes' plugins/platforms/a2a/ directory and summarized in the original A2A docs page.


8. Deployment Topologies & Hermes Best Practices

Hermes supports a spectrum of deployment topologies ranging from zero-isolation local experimentation to fully sandboxed, multi-agent production architectures.

Deployment Topology Spectrum

[1. Global Local]  ──>  [2. Scoped Home / Profiles]  ──>  [3. Sandboxed Execution]  ──>  [4. Fully Containerized]  ──>  [5. Isolated Worker VM]
(Simple dev machine)     (Isolated per project)           (Docker terminal backend)        (Hermes in s6 Container)      (Remote gateway + SSH)
  1. Global Local Setup (~/.hermes/):

    • Use Case: Personal single-agent development on a local machine.
    • Isolation: None. Hermes runs directly as your local user with full user-account filesystem and shell permissions.
  2. Project-Scoped Home (HERMES_HOME) or First-Class Profiles (hermes profile create): (This project's setup)

    • Use Case: Project-specific agents, multi-agent experimentation, or git-isolated repository workflows.
    • Native Profiles: Running hermes profile create coder creates ~/.hermes/profiles/coder/ and generates a native coder alias and launchd service slot.
    • Isolation: Config, memory, session history, and skills are isolated; terminal commands still run as the local OS user unless sandboxing is enabled.
  3. Sandboxed Command Execution (terminal.backend: docker / modal / daytona / vercel_sandbox):

    • Use Case: Production gateway deployments, untrusted input handling, or multi-tenant A2A endpoints.
    • Mechanism: Hermes runs on the host (or gateway), but every terminal command executed by the agent runs inside a single, persistent Docker sandbox container (--cap-drop ALL, no-new-privileges, pids-limit 256).
    • Best Practice: The security documentation explicitly recommends container backends for production gateways to eliminate dangerous command approval prompts entirely.
  4. Fully Containerized Hermes (nousresearch/hermes-agent image):

    • Use Case: Cloud servers, VPS deployments (Hetzner, AWS, GCP VM), or Kubernetes clusters.
    • Mechanism: Hermes itself runs inside Docker supervised by s6-overlay (PID 1). All profiles, gateways, and the web dashboard (:9119) run supervised inside one container with automatic restart-on-crash behavior.
  5. Isolated Worker VM / Network Boundary (terminal.backend: ssh):

    • Use Case: High-security enterprise environments or multi-agent testbeds.
    • Mechanism: The Hermes gateway process (handling messaging, LLM inference, memory, and A2A connections) runs on one machine, while command execution is routed over SSH (terminal.backend: ssh) to a separate worker VM or disposable instance.

Summary: Where This Setup Sits & Recommended Next Steps

  • Current Setup: Level 2 (Project-Scoped HERMES_HOME, Vertex AI ADC, local terminal execution, background gateway job).
  • Recommended Next Step for Local Persistence: Run run-hermes.sh gateway install --start-now --start-on-login to convert the ad hoc background job into an OS-supervised launchd service (Level 2 + Level 5 service layer).
  • Recommended Next Step for Security Hardening: If exposing A2A to remote peers or untrusted inputs, set terminal.backend: docker in .hermes/config.yaml to isolate tool-call execution inside a Docker container sandbox.

9. Troubleshooting & FAQ

  • Issue: "Vertex AI credentials could not be resolved"
    • Cause: GOOGLE_APPLICATION_CREDENTIALS/VERTEX_CREDENTIALS_PATH invalid, or ADC not logged in.
    • Fix: Run gcloud auth application-default login and ensure vertex.project_id is set in .hermes/config.yaml.
  • Issue: "Service account info was not in the expected format, missing fields token_uri, client_email"
    • Cause: Hermes tried to parse a plain ADC user-credentials file as a service-account key. See the note in §5 about the local vertex_adapter.py fallback patch.
    • Fix: Confirm the fallback to google.auth.load_credentials_from_file() is present in agent/vertex_adapter.py, or switch to a real service-account JSON via VERTEX_CREDENTIALS_PATH.
  • Issue: 404 Error on Gemini 3.x Models
    • Cause: Requested on a regional Vertex endpoint where 3.x preview models aren't deployed.
    • Fix: Set region: "global" in .hermes/config.yaml.
  • Issue: 403 Permission Denied on Vertex AI
    • Cause: GCP identity lacks Vertex AI access.
    • Fix: Grant roles/aiplatform.user on YOUR_GOOGLE_CLOUD_PROJECT to the ADC identity.
  • Issue: Warning: Unknown toolsets: a2a / outbound a2a_call not available in hermes chat
    • Cause: Known Hermes deferred-plugin-loading gap — see the boxed note in §6.
    • Fix: Route the request through the running gateway session (Scenario B/C) instead of standalone hermes chat.
  • Issue: a2acli serve --echo target rejects Hermes' a2a_call
    • Cause: a2acli serve --echo defaults to HTTP+JSON transport; Hermes' outbound client expects JSON-RPC.
    • Fix: Start the mock server with --transport jsonrpc explicitly.

10. External References & Resources

Hermes Agent & Nous Research

A2A (Agent-to-Agent) Specification & Tools

Google Cloud & Vertex AI