Start with one bounded GitHub issue. This guide prepares the worker policy, starts the published Docker image, clones the worker checkout, and shows the first-officer commands that follow the pull request through review and acceptance.
You remain the Captain. Your coding agent is the first officer: it runs on
your host with the Captain GitHub identity (captain_github_login in the
policy). Hermes is the worker: it runs in Docker with a separate GitHub
identity (worker_github_login). FAVA Trails, OpenViking, private company
repositories, and external skill packs are optional. The bundled
setup-helmet, helmet-issue, and helmet-epic skills are the first-officer
workflow.
- Docker and Docker Compose v2
- A GitHub token for the worker identity named in your policy
(
worker_github_login) - Python 3.11+ for the host CLI and tests
- Model-provider access for the Hermes worker
- For first-officer orchestration: the Captain GitHub identity and the
portable
helmet-issue/helmet-epicskills. The documented workflow has been exercised through Codex; Claude Code and Hermes have installation and static validation.
The published worker image is a separate installation from this CLI.
Installing helmet does not pull GHCR or start Docker. The current stable
digest, from the Hermes Helmet 1.0.0 release, is
ghcr.io/machinewisdomai/hermes-helmet/runtime@sha256:04f772b48151813aec165ec81ee7323e316cababa686963fbc4064a40b283c2b,
built from 24876c7583f5dd3219e94e27b72a8952ba9752aa. See
runtime-image.md. Development builds still pin the official
Hermes Agent base
nousresearch/hermes-agent:v0.21.6@sha256:55e192fba0cd4fde61142abbff5adeacff40efdb482ccd0ff877924bd274f909
(Hermes Agent 0.21.6). Override only with another immutable tag+digest; do not
use latest or main.
Clone and install the current public source:
git clone https://github.com/MachineWisdomAI/hermes-helmet.git
cd hermes-helmet
python3 -m venv .venv
. .venv/bin/activate
python -m pip install .
helmet --helpThe help output lists the host commands. Current source installs helmet
and its compatible alias hermes-helmet; the Python package is named
hermes-helmet. If you installed the September 23 preview wheel, use
hermes-helmet in place of helmet in these examples.
The published Compose stack reads deploy/.env and mounts a secret-free
policy file. Start there. The host setup wizard is a separate later path; it
does not write these files and does not mount them into the container.
cp deploy/.env.example deploy/.env
chmod 600 deploy/.env
# set HERMES_GITHUB_TOKEN= to the worker PAT
# set HERMES_HELMET_IMAGE to the published digest named in Prerequisites;
# do not copy the older digest from the deploy/.env.example comment
cp config/policy.example.json config/policy.json
# edit company, captain_github_login, worker_github_login, assignee,
# repositories[].slug, repositories[].worktreeKeep the token only in deploy/.env. Make that file owner-only before entering
the token. Do not commit it. The policy is secret-free public configuration
and must stay readable by container UID 10000.
Compose mounts
${HERMES_HELMET_POLICY_HOST_PATH:-../config/policy.json} read-only at
/opt/hermes-helmet/config/policy.mounted.json and runs as UID/GID 10000.
That host file must be readable by that user; the copied example is. At
startup the entrypoint copies the mount to
/opt/data/github-issue-poller/policy.json on the worker volume. Intake and
the poller use the copy, not the mount.
The example worktree /opt/data/repos/demo-repo is a path inside the worker
volume. It is created in the next container steps, not on the host. Host
helmet doctor --home inspects the machine that invoked it and will not see
that checkout.
render_crew_contract(policy) prints the crew contract from the policy. It
does not install instructions. After you create the assignee profile, copy
the reviewed contract into that profile's SOUL.md at
$HERMES_HOME/profiles/<assignee>/SOUL.md. ExampleCo uses profile builder
and Compose HERMES_HOME=/opt/data.
PYTHONPATH=src python3 - <<'PY'
from pathlib import Path
from hermes_helmet.authority import load_authority, render_crew_contract
policy = load_authority(Path("config/policy.json"))
print(render_crew_contract(policy))
PYFrom the repository root, pull the published digest and start Compose without
building. Put that digest in deploy/.env as HERMES_HELMET_IMAGE. Compose
interpolates image: from that file. A leftover shell export of
HERMES_HELMET_IMAGE overrides .env; docker pull "$HERMES_HELMET_IMAGE"
does not read .env. If this shell previously exported the variable, unset it
so .env wins, then pull and start through Compose:
unset HERMES_HELMET_IMAGE
docker compose --env-file deploy/.env -f deploy/compose.yaml pull hermes
docker compose --env-file deploy/.env -f deploy/compose.yaml up -d --no-buildThe image was built from 24876c7583f5dd3219e94e27b72a8952ba9752aa. Confirm
the running container recorded that commit. Publishing a release does not
install or restart a worker, so this check is how you learn what your
container runs:
docker compose --env-file deploy/.env -f deploy/compose.yaml exec hermes \
cat /opt/hermes-helmet/SOURCE_COMMITThe digest identifies the image built from that recorded source SHA.
SOURCE_COMMIT inside the container is that SHA. The image is the 1.0.0
stable release image; it is distributed beside, and separate from, the
release's wheel and other assets.
The image entrypoint writes the worker token to its owner-only runtime file,
copies the mounted policy into /opt/data/github-issue-poller/policy.json,
removes the raw token variables, and only then enters the upstream s6 init
chain. Supervised dashboard and gateway processes therefore do not inherit the
PAT, while /usr/local/bin/gh can read it for one authorized GitHub command.
To wrap deploy/Dockerfile around a local checkout instead, select
hermes-helmet:local explicitly. Unsetting the shell variable is not enough
when deploy/.env still pins the published digest:
export HERMES_HELMET_SOURCE_COMMIT="$(git rev-parse HEAD)"
export HERMES_HELMET_IMAGE=hermes-helmet:local
docker compose --env-file deploy/.env -f deploy/compose.yaml up -d --buildThat development SOURCE_COMMIT should match git rev-parse HEAD. Keep
company-specific Compose, policy, and volumes in a
private overlay.
The poller installer requires every policy repositories[].worktree to exist
as a Git checkout the worker can see. Compose persists that tree on the
hermes_state volume under /opt/data. The ExampleCo fixture uses slug
example-org/demo-repo and worktree /opt/data/repos/demo-repo. Replace those
placeholders with your allowlisted repository before enabling intake.
Do this in the worker container, with the worker GitHub identity. Do not clone into a first-officer checkout or reuse Captain credentials.
Set the wrapper-backed GitHub credential helper globally before cloning so
HTTPS clone can authenticate. After the checkout exists, set worker identity
and the same helper in that repository's local Git config. Linked worktrees
inherit those local values, including Kanban checkouts that use a different
home than Compose HOME. Do not run gh auth setup-git; it can record the
underlying gh binary and bypass /usr/local/bin/gh.
docker compose --env-file deploy/.env -f deploy/compose.yaml exec hermes \
mkdir -p /opt/data/repos
docker compose --env-file deploy/.env -f deploy/compose.yaml exec hermes \
git config --global --replace-all credential.https://github.com.helper \
'!gh auth git-credential'
docker compose --env-file deploy/.env -f deploy/compose.yaml exec hermes \
git clone https://github.com/example-org/demo-repo.git \
/opt/data/repos/demo-repo
docker compose --env-file deploy/.env -f deploy/compose.yaml exec hermes \
git -C /opt/data/repos/demo-repo config --local user.name "example-agent"
docker compose --env-file deploy/.env -f deploy/compose.yaml exec hermes \
git -C /opt/data/repos/demo-repo config --local user.email \
"example-agent@users.noreply.github.com"
docker compose --env-file deploy/.env -f deploy/compose.yaml exec hermes \
git -C /opt/data/repos/demo-repo config --local --replace-all \
credential.https://github.com.helper '!gh auth git-credential'The helper asks /usr/local/bin/gh for GitHub HTTPS credentials. That wrapper
reads the owner-only runtime token file. Do not put a token in the clone
command, in Git config, in policy, or in source files. The worker Git identity
is your worker_github_login (example-agent in the fixture), never
example-captain.
Confirm the path the installer will check, after it exists, inside the container that holds it:
docker compose --env-file deploy/.env -f deploy/compose.yaml exec hermes \
git -C /opt/data/repos/demo-repo rev-parse --is-inside-work-treeKanban work runs as the policy assignee Hermes profile (builder in the
ExampleCo fixture). Create that profile in the worker container, set the
policy provider and model, then complete that profile's own provider login.
Keep Captain and worker identities distinct: do not copy first-officer host
credentials, sessions, or profiles into the worker.
These hermes invocations are the in-container Hermes Agent CLI. They are not
the host Hermes Helmet command. Current source installs that host CLI as
helmet, with compatible alias hermes-helmet; the dated preview wheel
exposes only hermes-helmet.
docker compose --env-file deploy/.env -f deploy/compose.yaml exec hermes \
/opt/hermes/.venv/bin/hermes profile create builder
docker compose --env-file deploy/.env -f deploy/compose.yaml exec hermes \
/opt/hermes/.venv/bin/hermes -p builder config set model.provider openai
docker compose --env-file deploy/.env -f deploy/compose.yaml exec hermes \
/opt/hermes/.venv/bin/hermes -p builder config set model.default gpt-4.1
docker compose --env-file deploy/.env -f deploy/compose.yaml exec hermes \
/opt/hermes/.venv/bin/hermes -p builder config set agent.max_turns 2000
docker compose --env-file deploy/.env -f deploy/compose.yaml exec hermes \
/opt/hermes/.venv/bin/hermes -p builder auth add openaiauth add prompts locally for the credential. Do not pass --api-key or
put provider secrets in commands or files. Match model.provider /
auth add to your policy inference_provider and inference_model. Then
make one bounded smoke call:
docker compose --env-file deploy/.env -f deploy/compose.yaml exec hermes \
/opt/hermes/.venv/bin/hermes -p builder chat --oneshot --max-turns 1 \
-q "Reply with the single word ready."To run the worker on a Claude Pro or Max subscription instead of an API key, use the token and profile commands in Claude subscription workers, then make the same smoke call.
OpenViking, FAVA Trails, Signal, and company skill packs stay optional and remain disabled in the ExampleCo fixture. Do not enable them on this path.
docker compose --env-file deploy/.env -f deploy/compose.yaml exec hermes \
/opt/hermes/.venv/bin/python -m hermes_helmet.install_poller \
--config /opt/data/github-issue-poller/policy.json \
--skip-model-configUse --skip-model-config so install keeps the assignee profile you configured
and smoke-tested in the previous step instead of rewriting provider, model, and
max turns. The installer does not perform provider login, and this flag does
not prove credentials work; the bounded smoke call does. The installer still
validates allowlisted worktrees and reconciles exactly one no-agent cron job
(cron_deliver defaults to local).
Label an allowlisted open issue with the policy dispatch_label (default
hermes-kanban-go), then:
docker compose --env-file deploy/.env -f deploy/compose.yaml exec hermes \
/opt/hermes/.venv/bin/python -m hermes_helmet \
--config /opt/data/github-issue-poller/policy.jsonAn eligible issue should now have one linked Kanban root task. Repeating intake for that issue reuses the existing task. Follow the worker's linked pull request when implementation finishes; task completion alone does not accept or merge it.
On the Captain host (not the worker container identity), install the bundled
skills with helmet install-skills as below, or use the
first-officer plugin for Claude Code or Codex.
Plugin installation copies instructions only; it does not provision Docker,
tokens, or worker access. After setup, reuse policy and worker settings across
sessions with a user-owned env file; see
Starting a first-officer session.
PYTHONPATH=src python3 -m hermes_helmet.cli install-skills --target codex
PYTHONPATH=src python3 -m hermes_helmet.cli issue \
https://github.com/example-org/demo-repo/issues/123 \
--config config/policy.json \
--host-continuation none --one-pass-only
PYTHONPATH=src python3 -m hermes_helmet.cli status \
https://github.com/example-org/demo-repo/issues/123 \
--config config/policy.json
PYTHONPATH=src python3 -m hermes_helmet.cli wait \
https://github.com/example-org/demo-repo/issues/123 \
--timeout-seconds 1800
PYTHONPATH=src python3 -m hermes_helmet.cli epic \
https://github.com/example-org/demo-repo/issues/42 \
--config config/policy.json \
--accept-graph --host-continuation none --one-pass-only
PYTHONPATH=src python3 -m hermes_helmet.cli epic-status \
https://github.com/example-org/demo-repo/issues/42 \
--config config/policy.jsonwait holds one bounded, read-only worker-side process. Exit 0 means run one
fresh issue or epic pass. Exit 2 is a clean timeout. Pass the returned cursor
to the next wait after every outcome. Exit 1 is a transport or contract
failure.
See helmet-issue.md and helmet-epic.md. To have a worker write a DOCX or PPTX instead, follow the runnable walkthrough in Fresh writing worker and Helmet Write.
helmet setup configures the host that runs first-officer tools. It writes
~/.hermes-helmet/policy.json, owner-only secrets, and bundled skills. It does
not start Docker, does not write deploy/.env, and does not mount its output
into Compose.
cp config/setup.answers.example.json /private/path/setup.answers.json
# replace every ExampleCo identity, repository, checkout, and provider value
helmet setup --answers /private/path/setup.answers.json --jsonSetup prompts locally without echo for the worker PAT and provider credential.
It writes both only under ~/.hermes-helmet/secrets with owner-only modes.
Keep the answers file secret-free. Put OpenViking and FAVA Trails
selected/confirmed (or a decline) in that file before setup; both stay
disabled unless those values are true when the policy is written. Matt Pocock
skills and gstack remain pinned recommendations and are never installed by
setup.
The default live model probe supports OpenAI. Another provider must expose an
OpenAI-compatible endpoint configured as
model_lanes.hermes_executor.base_url; unsupported providers fail before setup
creates labels or writes state. Hermes Helmet state and secrets directories must
be owned by the current user with mode 0700, credential files must be regular
owner-owned 0600 files, and symlinks are rejected. Before prompting for
credentials, probing providers, creating labels, or writing state, setup
preflights every bundled-skill destination. Any foreign skill conflict stops
setup without changing local or GitHub state.
The host wizard requires those Git checkout roots, with matching origins, to exist on this host before doctor. The Docker bootstrap uses container paths and its container checks; host doctor does not see them.
After those host paths exist, diagnose on this host:
helmet doctor --config ~/.hermes-helmet/policy.json --home ~ --live --jsonSetup-state doctor (--home) is a pre-dispatch gate for this filesystem. It
always verifies the live worker identity, configured work/action allowlist,
labels, and provider/model even if --live is omitted. It also checks every
existing component of the supplied home, Hermes Helmet state/generated/secret
paths, and Codex/Claude/Hermes skill roots. It checks every configured origin
fetch and push URL against the repository slug. Missing checkouts fail; doctor
does not skip them. Worktree paths that exist only inside the worker
container, such as /opt/data/repos/demo-repo, belong to the Docker route.
A shared policy is usable only when those exact worktree paths are valid in
each environment that consumes them. Otherwise keep the host wizard policy
and the Compose mount as separate files. Setup writes the generated policy
mode 0600 for the host user; the container user is 10000 and often cannot
read that file. A Compose copy must be readable by UID 10000. Restart
Compose after changing the mounted file so the entrypoint recopy runs.
First-officer host commands can keep HERMES_HELMET_CONFIG pointed at the
generated policy; that does not change the worker mount.
If an unexpected skill apply failure occurs after preflight, the atomic skill transaction rolls back new destinations and setup records an incomplete, resumable skills stage. After removing a conflict, rerunning the same setup reuses the PAT and any labels already created.
See setup-helmet.md.
- Creates one Kanban root task per eligible issue (idempotent by issue URL)
- Ignores closed, unlabeled, malformed, and non-allowlisted issues (the
Captain-side
helmet prepare-reponamed-request enrollment path can admit an exact requested repository for first-officer workflows; the poller itself stays on the static allowlist) - After a PR exists, trusted review activity creates one dependent same-PR repair
- Coalesces activity behind an outstanding repair; later activity can succeed it
- Never merges from the worker and never force-pushes
- Verifies the live GitHub login is the configured worker (Captain/unknown/mismatch fail closed)
- First-officer
helmet-issueadopts work, reviews heads, and applies merge gates without a second poller - First-officer
helmet-epicvalidates parent/child graphs and invokes helmet-issue with bounded parallelism
Optional services and external skill import are intentionally out of this quickstart. See fava-trails.md when enabling the optional governed company brain after the minimum runtime is healthy. OpenViking working context is optional and documented in openviking.md. When you add a company pack later, see company-skills.md. Optional local or hosted model lanes are documented in model-lanes.md; omit them for the minimum runtime.