Connect a Hermes Agent to T3 Code

An agent-ready runbook for installing the experimental in-repo gateway on another tailnet machine.

Enrollment secrets stay out of this page

Install the plugin first, then ask the operator for a fresh one-time command generated in T3 Code. Never paste a real enrollment token, saved credential, or Hermes environment file into a report.

Current experimental target

T3 Code
https://siva.otter-hawksbill.ts.net:7446/
Repository
https://github.com/pingdotgg/t3code.git
Branch
experiment/hermes-provider
Plugin source
integrations/hermes-t3-gateway
Network
Tailnet only

Use the exact siva.otter-hawksbill.ts.net hostname. Do not substitute siva.davis7.space for this HTTPS port; that alias does not currently have a valid certificate for port 7446. Hermes opens an outbound WebSocket, so the Hermes machine needs no public listener or inbound firewall rule.

1. Preflight

Run these checks on the machine that already owns the Hermes Agent:

command -v git
command -v hermes
hermes --version
tailscale status
hermes gateway list
curl -fsSI --max-time 10 https://siva.otter-hawksbill.ts.net:7446/ | head

The HTTP check must return 200. Stop on a timeout, DNS error, TLS error, or non-200 response and ask the operator to restore the experimental T3 environment. Do not silently use the production T3 instance.

The Hermes installation must support hermes plugins enable, hermes gateway, Python 3.11–3.13, and Hermes' normal websockets dependency.

Select exactly one Hermes profile

For the default profile:

HERMES_HOME_DIR="${HERMES_HOME:-$HOME/.hermes}"
hermes_cmd() { hermes "$@"; }

For a named profile, replace PROFILE_NAME with the profile reported by hermes gateway list:

HERMES_PROFILE="PROFILE_NAME"
HERMES_HOME_DIR="$HOME/.hermes/profiles/$HERMES_PROFILE"
hermes_cmd() { hermes -p "$HERMES_PROFILE" "$@"; }

Keep this shell open. Use hermes_cmd for every remaining Hermes command so installation, enrollment, and the running gateway all target the same profile.

2. Clone the experimental branch

Use a fresh path. Stop rather than overwrite an existing checkout.

T3CODE_DIR="$HOME/Developer/t3code-hermes-provider"
if [ -e "$T3CODE_DIR" ]; then
  echo "STOP: $T3CODE_DIR already exists; inspect it or choose a new path." >&2
  exit 1
fi
mkdir -p "$(dirname "$T3CODE_DIR")"
git clone --branch experiment/hermes-provider --single-branch \
  https://github.com/pingdotgg/t3code.git \
  "$T3CODE_DIR"
git -C "$T3CODE_DIR" status --short --branch
git -C "$T3CODE_DIR" log -1 --oneline --decorate

The checked-out branch must be experiment/hermes-provider. Record the current commit hash in the final report.

3. Install and enable the plugin

PLUGIN_SOURCE="$(cd "$T3CODE_DIR/integrations/hermes-t3-gateway" && pwd -P)"
PLUGIN_LINK="$HERMES_HOME_DIR/plugins/hermes-t3-gateway"
mkdir -p "$HERMES_HOME_DIR/plugins"

if [ -L "$PLUGIN_LINK" ]; then
  ls -ld "$PLUGIN_LINK"
  echo "STOP if this does not point to $PLUGIN_SOURCE"
elif [ -e "$PLUGIN_LINK" ]; then
  echo "STOP: $PLUGIN_LINK exists and is not a symlink." >&2
  exit 1
else
  ln -s "$PLUGIN_SOURCE" "$PLUGIN_LINK"
fi

hermes_cmd plugins enable hermes-t3-gateway
hermes_cmd plugins list --user --plain | grep hermes-t3-gateway
hermes_cmd t3 status

Before pairing, hermes_cmd t3 status should say T3 Code: not enrolled. “Unknown command” means the plugin was enabled in the wrong profile or Hermes lacks the required plugin CLI surface.

Optional source smoke test:

python3 -m unittest discover \
  "$T3CODE_DIR/integrations/hermes-t3-gateway/tests" \
  -p 'test_*.py'

4. Create and consume a fresh enrollment

The operator performs these steps in T3 Code:

  1. Open https://siva.otter-hawksbill.ts.net:7446/.
  2. Go to Settings → Providers → Add provider → Hermes.
  3. Choose a unique name such as hermes-research or hermes-nexus.
  4. Keep the connector URL at the browser origin unless another reachable T3 origin is intentional.
  5. Create enrollment and deliver the complete one-time command directly to the setup agent.

The generated command has this shape. Do not run this placeholder:

hermes t3 connect \
  --url https://siva.otter-hawksbill.ts.net:7446 \
  --token '<fresh-one-time-token>'

Run the exact generated command through the selected profile:

hermes_cmd t3 connect \
  --url https://siva.otter-hawksbill.ts.net:7446 \
  --token '<paste-the-fresh-token-here>'

hermes_cmd t3 status

The token expires after 10 minutes, is single-use, and is invalidated when a newer token is created for that instance. The long-lived credential is delivered over the WebSocket and stored through Hermes' profile-aware configuration helper; it is never printed.

5. Restart the correct Hermes gateway

hermes_cmd gateway status --deep --full

If a gateway service already owns this profile:

hermes_cmd gateway restart

If no service exists on a normal Linux or macOS host:

hermes_cmd gateway install --start-now --start-on-login

For WSL without systemd or an intentionally foreground-managed deployment, use exactly one supervisor, for example one tmux session:

hermes_cmd gateway run -v

Do not launch a foreground gateway when a service already owns the profile. Docker, s6, and existing system-level installations should keep their current process manager and restart that owner.

6. End-to-end verification

  1. In T3 Settings, refresh the Hermes instance and confirm Connected.
  2. Confirm plugin version, Hermes version, protocol version, and last-connected time are populated.
  3. Start a new T3 thread and select the named Hermes instance in the normal provider selector.
  4. Send Reply exactly HERMES_GATEWAY_OK and confirm text streams live.
  5. Send another message in the same T3 thread and confirm it continues the same Hermes session.
  6. Optionally send during an active turn and confirm it steers the running Hermes turn.
  7. Confirm mapped tool activity appears in T3's activity UI.
hermes_cmd t3 status
hermes_cmd gateway status --deep --full

A final report may include the machine/profile, branch and commit, plugin symlink and source, enabled state, redacted hermes t3 status, gateway service state, T3 provider state, and streaming result. Never include a token, credential, environment-file contents, or copied pairing command.

Update later

git -C "$T3CODE_DIR" status --short --branch
git -C "$T3CODE_DIR" fetch origin experiment/hermes-provider
git -C "$T3CODE_DIR" switch experiment/hermes-provider
git -C "$T3CODE_DIR" pull --ff-only origin experiment/hermes-provider
hermes_cmd gateway restart

Stop if the checkout has local changes. The symlink updates the source in place, but the long-running gateway must restart to load new Python code.

Troubleshooting

Enrollment is invalid, expired, or already used

Ask the operator for a fresh command and run it exactly once. A newer command invalidates the older token.

T3 remains Offline

hermes t3 connect saves credentials; it does not reload a running gateway. Verify the selected profile, restart its owning service, and confirm only one gateway process uses that profile.

The connection alternates between machines

The same credential is active in more than one Hermes process. Stop the duplicate. Each Hermes agent needs its own T3 provider instance and enrollment.

The instance was revoked or removed

Revocation permanently invalidates that credential. Remove the dead instance in T3, add a fresh instance, enroll it, and restart Hermes. Historical T3 threads remain readable and bound to the unavailable old identity; they are never reassigned.

Upgrade required

Fast-forward the experimental branch and restart Hermes. If the warning remains, report the T3 commit and hermes --version; do not bypass protocol validation.

Generic activity or no attachments

This is expected in V1. Activity that does not map cleanly uses a meaningful generic payload. Attachments intentionally remain unsupported until the gateway is stable.