Important
Project status: Retired / Unsupported
Hermes Outpost is no longer actively maintained. This repository remains available for existing users and historical reference, but no further releases, fixes, compatibility updates, or support are planned.
Hermes Outpost is a standalone community plugin for Hermes Agent that dispatches durable tasks to independent Hermes agents on SSH-reachable machines.
A controller Hermes can hand a job to another computer, keep working, poll job state, and later retrieve the remote Hermes result and session ID. The worker uses its own filesystem, Hermes profile, plugins, and model credentials.
Hermes already has remote terminal execution and local child-agent delegation. Outpost adds a different primitive:
- SSH terminal backend: the current Hermes process runs shell commands remotely.
delegate_task: a child agent runs locally.- Hermes Outpost: a separate Hermes process and session runs on another configured machine.
The controller sends the task over stdin; it does not embed task text in the SSH command line.
Hermes Outpost registers exactly four tools:
remote_delegate_task— start an asynchronous remote Hermes job and return a durablejob_id.remote_worker_status— inspect job state and recent meaningful activity.remote_worker_result— retrieve the final result, remote session ID, token metadata, logs, and exit status.remote_worker_cancel— cancel the owned controller runner and SSH connection.
- Linux
- Hermes Agent
>=0.21 - Python 3.11+
- OpenSSH client
WSL2 counts as Linux from the plugin's perspective. Native macOS and native Windows controllers are not claimed by v0.1.0 because they have not been exercised end-to-end.
- SSH-reachable POSIX-compatible login shell
- Hermes installed and configured
- Non-interactive OpenSSH authentication
- Its own provider/model credentials
The remote Hermes may use a different model/provider from the controller.
The v0.1.0 release was validated end-to-end on:
Ubuntu/Linux controller
Hermes v0.21.2
│
│ OpenSSH
▼
Ubuntu under WSL2
Hermes v0.21.0
│
▼
remote model inference
│
▼
result + remote session ID returned to controller
The live run completed successfully using Outpost's v0.21.0 quiet-text compatibility path. The structured stream-json path is covered by the automated integration suite.
After catalog acceptance:
hermes plugins install hermes-outpost
hermes plugins enable hermes-outpostBefore catalog acceptance, or for development:
hermes plugins install https://github.com/keeltrace/hermes-outpost.git --no-enable
hermes plugins enable hermes-outpostThen validate the installed copy:
hermes plugins doctor hermes-outpost --ciSee INSTALL.md for configuration and smoke testing.
First make normal OpenSSH work non-interactively. For example:
Host gpu-box
HostName 100.92.14.31
User worker
IdentityFile ~/.ssh/id_ed25519Then add plugin settings to the active Hermes profile's config.yaml:
plugins:
entries:
hermes-outpost:
settings:
default_host: gpu
hosts:
gpu:
ssh_host: gpu-box
workspace_root: /home/worker/projects
default_workspace: /home/worker/projects/example
profile: remote-worker
allowed_profiles: [remote-worker]
max_turns: 250
task_timeout_seconds: 7200A remote profile is optional. If omitted, the worker uses its default Hermes profile.
Optional per-host settings include hermes_binary, ssh_binary, provider, model, toolsets, connect_timeout_seconds, max_turns, and task_timeout_seconds.
Ask the controller Hermes to delegate a suitable task. The model can call:
remote_delegate_task(
host="gpu",
workspace="example",
goal="Run the failing test, identify the root cause, fix it, and report verification."
)
The tool returns quickly with a job ID:
{"success":true,"job_id":"rw_...","status":"starting","host":"gpu"}Then use:
remote_worker_status(job_id="rw_...")
remote_worker_result(job_id="rw_...")
or:
remote_worker_cancel(job_id="rw_...")
Outpost is deliberately configuration-driven:
- The model selects only administrator-defined host aliases.
ssh_hostis a single OpenSSH destination/Host alias, never arbitrary SSH options.- OpenSSH retains normal host-key verification.
- Outpost does not read private keys or collect SSH passwords.
- Controller model/API credentials are not forwarded to the remote worker.
- Goal/context text is sent on stdin, not placed in SSH argv or the remote shell command.
- Caller-selected workspaces must remain under a configured
workspace_root. - The remote launch performs a second
pwd -Pcontainment check to reject symlink escapes. - Remote profile overrides require an explicit allowlist.
- Time and turn limits are capped by administrator configuration.
See SECURITY.md for the full boundary.
Job state is stored under the active Hermes profile:
$HERMES_HOME/plugin-data/hermes-outpost/
Metadata uses SQLite/WAL. Per-job event, stderr, and runner logs live under jobs/<job_id>/.
Each job is owned by a short-lived detached controller-side runner. There is no permanent Outpost daemon.
Outpost checks the remote Hermes CLI before launch:
- If
hermes chat --helpexposes--format, it uses structuredstream-jsonoutput. - Hermes v0.21.0 falls back to
hermes chat -Q, with the final answer on stdout andsession_id:captured from stderr.
Both modes are normalized into the same durable result contract.
The test suite uses only the Python standard library and a fake SSH executable:
python3 -m unittest discover -s tests -v
python3 -m compileall -q .
git diff --checkWith Hermes installed:
hermes plugins doctor /path/to/hermes-outpost --ciThis release intentionally does not implement automatic worker selection, queues, Kanban scheduling, artifact synchronization, session resume, a custom network daemon, or arbitrary SSH command execution. Those can be layered on after the small delegation primitive has broader field validation.
MIT. See LICENSE.