|
| 1 | +# Hermes Agent Orchestration Service ("Olympian") |
| 2 | + |
| 3 | +An **AI dark factory**: label a GitHub issue and the service drives the |
| 4 | +[Hermes Agent](https://github.com/nousresearch/hermes-agent) CLI through the entire |
| 5 | +delivery lifecycle — planning, implementation, self-review, and a draft PR — with a |
| 6 | +human only ever approving the plan and the final PR. No human writes code. |
| 7 | + |
| 8 | +``` |
| 9 | +issue labeled ──▶ PLAN ──▶ (human approves plan) ──▶ IMPLEMENT ──▶ SELF-REVIEW ⇄ REVISE |
| 10 | + ▲ │ │ |
| 11 | + └─ feedback ──┘ (confidence ≥ threshold) |
| 12 | + │ |
| 13 | + ▼ |
| 14 | + (human approves PR) ◀── AWAIT PR REVIEW ◀── OPEN DRAFT PR |
| 15 | + │ ▲ |
| 16 | + ▼ └── changes requested ──▶ IMPLEMENT |
| 17 | + DONE |
| 18 | +``` |
| 19 | + |
| 20 | +## How it works |
| 21 | + |
| 22 | +1. **Trigger.** An issue is labeled with the trigger label (default `hermes`). A `Job` is created. |
| 23 | +2. **Plan.** Hermes reads the issue (in a clone of the repo) and posts an implementation |
| 24 | + plan as an issue comment. |
| 25 | +3. **Plan approval loop.** A maintainer replies `/hermes approve` to proceed, or leaves a |
| 26 | + comment with corrections — each comment re-plans until approved (capped by `MAX_PLAN_REVISIONS`). |
| 27 | +4. **Implement.** Hermes writes the code on a branch (`hermes/issue-<n>`). An optional |
| 28 | + `VERIFY_COMMAND` (tests/build) gates the work, iterating up to `MAX_IMPLEMENTATION_ITERATIONS`. |
| 29 | +5. **Self-review.** Hermes reviews its own diff and returns a JSON verdict |
| 30 | + `{confidence, verdict, issues[]}`. Below `REVIEW_CONFIDENCE_THRESHOLD` it revises and |
| 31 | + re-reviews, up to `MAX_REVIEW_PASSES`. |
| 32 | +6. **Draft PR.** The branch is pushed and a **draft** PR is opened, linking the issue. |
| 33 | +7. **PR approval loop.** Approve the PR review → the job is `DONE`. Request changes → the |
| 34 | + implementation loop runs again and pushes an update. |
| 35 | + |
| 36 | +The orchestrator owns git; Hermes only edits files. **Prisma (SQLite) is the source of |
| 37 | +truth** for every job's state, plan revisions, agent runs, reviews, and the work queue — |
| 38 | +each Hermes prompt is rebuilt deterministically from the database. |
| 39 | + |
| 40 | +## Tech stack |
| 41 | + |
| 42 | +- **NestJS** (ESM, `"type": "module"`, NodeNext) — modular service. |
| 43 | +- **Prisma + SQLite** — file-based; no DB server to provision. |
| 44 | +- **DB-backed queue** — a polling worker claims tasks with an atomic `UPDATE ... RETURNING` |
| 45 | + (SQLite-safe), with exponential backoff/retry and per-job concurrency isolation. |
| 46 | +- **GitHub App** — webhooks, scoped installation tokens, comments, draft PRs, reviews. |
| 47 | +- **Hermes Agent CLI** — invoked headless: `hermes -z --yolo --source tool --max-turns N`. |
| 48 | + |
| 49 | +Module layout (one service per module): `config`, `prisma`, `metrics`, `health`, |
| 50 | +`github-app`, `github-api`, `webhook`, `job`, `queue`, `worker`, `agent`, `workspace`, |
| 51 | +`review`, `orchestrator`. |
| 52 | + |
| 53 | +## Quick start (local) |
| 54 | + |
| 55 | +```bash |
| 56 | +cd api |
| 57 | +cp .env.example .env # fill in GitHub App + Hermes values |
| 58 | +npm install |
| 59 | +npx prisma migrate dev # creates prisma/dev.db |
| 60 | +npm run start:dev |
| 61 | +``` |
| 62 | + |
| 63 | +Probe it: |
| 64 | + |
| 65 | +```bash |
| 66 | +curl localhost:3000/health # liveness |
| 67 | +curl localhost:3000/health/ready # readiness (DB) |
| 68 | +curl localhost:3000/metrics # Prometheus metrics |
| 69 | +``` |
| 70 | + |
| 71 | +## Required external setup |
| 72 | + |
| 73 | +These can't be scripted for you and gate the live run (not the build or tests): |
| 74 | + |
| 75 | +### 1. Register a GitHub App |
| 76 | +- **Permissions:** Issues *Read & write*, Pull requests *Read & write*, Contents *Read & |
| 77 | + write*, Metadata *Read-only*. |
| 78 | +- **Subscribe to events:** Issues, Issue comment, Pull request review, Installation. |
| 79 | +- **Webhook URL:** `https://<your-host>/webhooks/github` and a **webhook secret**. |
| 80 | +- Generate a **private key** (PEM). Install the App on the target repos. |
| 81 | +- Put the values in `.env`: `GITHUB_APP_ID`, `GITHUB_WEBHOOK_SECRET`, and either |
| 82 | + `GITHUB_APP_PRIVATE_KEY` (inline, `\n`-escaped) or `GITHUB_APP_PRIVATE_KEY_PATH`. |
| 83 | + |
| 84 | +For local dev, tunnel webhooks with [smee.io](https://smee.io) or ngrok to |
| 85 | +`localhost:3000/webhooks/github`. |
| 86 | + |
| 87 | +### 2. Provision Hermes |
| 88 | +- Install the `hermes` CLI (set `HERMES_BIN`) **or** build the sandbox image |
| 89 | + (`docker build -f Dockerfile.agent -t hermes-agent .`) and set `SANDBOX_MODE=docker`. |
| 90 | +- Configure provider credentials/model under `HERMES_HOME` (`~/.hermes`), or set |
| 91 | + `HERMES_MODEL`/`HERMES_PROVIDER`. |
| 92 | + |
| 93 | +## Using it |
| 94 | + |
| 95 | +1. Label an issue `hermes` (or your `TRIGGER_LABEL`). |
| 96 | +2. Hermes posts a plan. Reply with comments to iterate, or `/hermes approve` to build. |
| 97 | +3. A draft PR appears. **Approve** the PR review to finish, or **request changes** to loop. |
| 98 | + |
| 99 | +Issue-comment commands (maintainers only — write access required): |
| 100 | +- `/hermes approve` — approve the plan and start implementation |
| 101 | +- `/hermes cancel` — stop the job |
| 102 | +- `/hermes status` — report current state |
| 103 | + |
| 104 | +## Sandboxing |
| 105 | + |
| 106 | +- `SANDBOX_MODE=none` (default) runs `hermes` as a subprocess in the job's worktree. |
| 107 | +- `SANDBOX_MODE=docker` runs each agent invocation inside `DOCKER_AGENT_IMAGE`, mounting |
| 108 | + **only** that job's directory and the read-only Hermes config. Each job lives in its own |
| 109 | + directory keyed by job id, so raising `WORKER_CONCURRENCY` runs N isolated jobs in parallel. |
| 110 | + |
| 111 | +## Configuration |
| 112 | + |
| 113 | +All config is validated at boot (see `src/config/config.model.ts`). Full reference and |
| 114 | +defaults live in [`api/.env.example`](api/.env.example). Key knobs: |
| 115 | + |
| 116 | +| Variable | Purpose | |
| 117 | +| --- | --- | |
| 118 | +| `TRIGGER_LABEL` | Label that starts a job (default `hermes`). | |
| 119 | +| `REVIEW_CONFIDENCE_THRESHOLD` | Min self-review confidence to open a PR (default 85). | |
| 120 | +| `MAX_PLAN_REVISIONS` / `MAX_IMPLEMENTATION_ITERATIONS` / `MAX_REVIEW_PASSES` | Loop caps. | |
| 121 | +| `WORKER_CONCURRENCY` | Parallel jobs (default 2). | |
| 122 | +| `VERIFY_COMMAND` | Optional tests/build command used as an acceptance gate. | |
| 123 | +| `SANDBOX_MODE` | `none` or `docker`. | |
| 124 | +| `HERMES_BIN` / `HERMES_HOME` / `HERMES_MODEL` / `HERMES_MAX_TURNS` | Hermes invocation. | |
| 125 | + |
| 126 | +## Docker |
| 127 | + |
| 128 | +```bash |
| 129 | +cd api && cp .env.example .env # fill in values |
| 130 | +docker compose up --build # from repo root: builds the api image |
| 131 | +``` |
| 132 | + |
| 133 | +SQLite lives on a named volume; there is no separate database container. |
| 134 | + |
| 135 | +## Operations |
| 136 | + |
| 137 | +- **Health:** `/health` (liveness), `/health/ready` (DB). |
| 138 | +- **Metrics:** `/metrics` — job counts by state, queue depth, agent run durations, webhook |
| 139 | + counts, last review confidence. |
| 140 | +- **Audit trail:** every state transition (`JobStateTransition`) and every Hermes |
| 141 | + invocation (`AgentRun`) is persisted; inspect with `npx prisma studio`. |
| 142 | +- **Retries:** failed tasks back off exponentially up to `QUEUE_MAX_ATTEMPTS`; exhausted |
| 143 | + jobs are failed and a comment is posted. Orphaned tasks are reclaimed after `QUEUE_LOCK_TTL_MS`. |
| 144 | + |
| 145 | +## Tests |
| 146 | + |
| 147 | +```bash |
| 148 | +cd api |
| 149 | +npm test # unit |
| 150 | +npm run test:e2e # full webhook→plan→approve→implement→review→PR loop with a stub Hermes |
| 151 | +``` |
| 152 | + |
| 153 | +The e2e suite stubs the Hermes agent and fakes the GitHub API, so the entire pipeline runs |
| 154 | +in CI without real LLM or GitHub credentials. |
0 commit comments