Skip to content

Commit 3f244a5

Browse files
committed
feat: provisioned hermes agent orchestration
0 parents  commit 3f244a5

83 files changed

Lines changed: 14948 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
jobs:
9+
build-and-test:
10+
runs-on: ubuntu-latest
11+
defaults:
12+
run:
13+
working-directory: api
14+
env:
15+
# Tests/build use an ephemeral SQLite file — no external services needed.
16+
DATABASE_URL: file:./ci.db
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- uses: actions/setup-node@v4
21+
with:
22+
node-version: 20
23+
cache: npm
24+
cache-dependency-path: api/package-lock.json
25+
26+
- run: npm ci
27+
28+
- name: Generate Prisma client
29+
run: npx prisma generate
30+
31+
- name: Apply migrations
32+
run: npx prisma migrate deploy
33+
34+
- name: Lint
35+
run: npm run lint
36+
37+
- name: Typecheck
38+
run: npm run typecheck
39+
40+
- name: Build
41+
run: npm run build
42+
43+
- name: Unit tests
44+
run: npm test
45+
46+
- name: E2E tests
47+
run: npm run test:e2e

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"prisma.pinToPrisma6": true
3+
}

README.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
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.

api/.dockerignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
node_modules
2+
dist
3+
coverage
4+
.workspaces
5+
*.log
6+
.env
7+
.env.*
8+
!.env.example
9+
prisma/dev.db*
10+
*.pem
11+
.git

api/.env.example

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# ── Server ────────────────────────────────────────────────────────────────
2+
PORT=3000
3+
NODE_ENV=development
4+
LOG_LEVEL=info
5+
6+
# ── Database (SQLite, file-based — no server to provision) ──────────────────
7+
# Path is relative to the prisma/ directory (so this resolves to api/prisma/dev.db).
8+
DATABASE_URL=file:./dev.db
9+
10+
# ── GitHub App ───────────────────────────────────────────────────────────────
11+
# From the GitHub App settings page.
12+
GITHUB_APP_ID=000000
13+
# The webhook secret you configured on the App.
14+
GITHUB_WEBHOOK_SECRET=replace-me
15+
# The App private key (PEM). Either inline (with literal \n) or a file path via GITHUB_APP_PRIVATE_KEY_PATH.
16+
GITHUB_APP_PRIVATE_KEY=
17+
GITHUB_APP_PRIVATE_KEY_PATH=./github-app.pem
18+
19+
# ── Hermes Agent CLI ─────────────────────────────────────────────────────────
20+
HERMES_BIN=hermes
21+
# HERMES_HOME holds Hermes config + provider credentials (~/.hermes by default).
22+
HERMES_HOME=
23+
# Model/provider overrides (optional). Falls back to Hermes config when unset.
24+
HERMES_MODEL=
25+
HERMES_PROVIDER=
26+
# Max tool-calling iterations per Hermes turn.
27+
HERMES_MAX_TURNS=90
28+
# Hard wall-clock timeout (ms) for a single Hermes invocation.
29+
HERMES_TIMEOUT_MS=1800000
30+
31+
# ── Orchestration policy ─────────────────────────────────────────────────────
32+
# Only issues with this label are picked up.
33+
TRIGGER_LABEL=hermes
34+
# Review confidence (0-100) required before opening a PR.
35+
REVIEW_CONFIDENCE_THRESHOLD=85
36+
MAX_PLAN_REVISIONS=10
37+
MAX_IMPLEMENTATION_ITERATIONS=5
38+
MAX_REVIEW_PASSES=5
39+
# Slash-command prefix used in issue comments (e.g. "/hermes approve").
40+
COMMAND_PREFIX=/hermes
41+
42+
# ── Queue / worker ───────────────────────────────────────────────────────────
43+
WORKER_ENABLED=true
44+
WORKER_CONCURRENCY=2
45+
# How often the worker polls for due tasks (ms).
46+
WORKER_POLL_INTERVAL_MS=2000
47+
QUEUE_MAX_ATTEMPTS=3
48+
# Backoff base (ms); delay = base * 2^(attempt-1).
49+
QUEUE_BACKOFF_BASE_MS=15000
50+
# A task RUNNING longer than this (ms) is considered orphaned and reclaimable.
51+
QUEUE_LOCK_TTL_MS=3600000
52+
53+
# ── Workspace / git ──────────────────────────────────────────────────────────
54+
WORKSPACE_ROOT=./.workspaces
55+
GIT_AUTHOR_NAME=Hermes Agent
56+
GIT_AUTHOR_EMAIL=hermes@users.noreply.github.com
57+
# Branch name template; {issue} is replaced with the issue number.
58+
BRANCH_PREFIX=hermes/issue-
59+
60+
# ── Sandbox ──────────────────────────────────────────────────────────────────
61+
# none = run hermes as a local subprocess; docker = run hermes inside a container.
62+
SANDBOX_MODE=none
63+
DOCKER_AGENT_IMAGE=hermes-agent:latest
64+
# Optional: command the agent should run to verify acceptance criteria (tests/build).
65+
VERIFY_COMMAND=

api/.gitignore

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# dependencies
2+
node_modules/
3+
4+
# build
5+
dist/
6+
*.tsbuildinfo
7+
8+
# test
9+
coverage/
10+
11+
# env & secrets
12+
.env
13+
.env.*
14+
!.env.example
15+
*.pem
16+
17+
# logs
18+
*.log
19+
logs/
20+
21+
# runtime
22+
.DS_Store
23+
24+
# agent workspaces (cloned repos / worktrees)
25+
.workspaces/
26+
27+
# sqlite database files (migrations are tracked; the .db files are not)
28+
prisma/*.db
29+
prisma/*.db-journal
30+
prisma/*.db-wal
31+
prisma/*.db-shm

api/.prettierrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"singleQuote": true,
3+
"trailingComma": "all",
4+
"printWidth": 100,
5+
"tabWidth": 2,
6+
"semi": true
7+
}

api/Dockerfile

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# ── Build stage ──────────────────────────────────────────────────────────────
2+
FROM node:20-alpine AS builder
3+
WORKDIR /app
4+
RUN apk add --no-cache openssl
5+
COPY package*.json ./
6+
RUN npm ci
7+
COPY prisma ./prisma
8+
RUN npx prisma generate
9+
COPY tsconfig*.json nest-cli.json ./
10+
COPY src ./src
11+
RUN npm run build
12+
13+
# ── Runtime stage ────────────────────────────────────────────────────────────
14+
FROM node:20-alpine AS runtime
15+
WORKDIR /app
16+
ENV NODE_ENV=production
17+
# git: required by the workspace module (clone/commit/push).
18+
# openssl + ca-certificates: Prisma engine + HTTPS git remotes.
19+
# NOTE: with SANDBOX_MODE=none the `hermes` binary must also be on PATH in this
20+
# image; with SANDBOX_MODE=docker the agent runs in DOCKER_AGENT_IMAGE instead
21+
# (mount the Docker socket and set the image).
22+
RUN apk add --no-cache git openssh-client ca-certificates openssl
23+
COPY package*.json ./
24+
COPY --from=builder /app/node_modules ./node_modules
25+
COPY --from=builder /app/dist ./dist
26+
COPY --from=builder /app/prisma ./prisma
27+
COPY docker-entrypoint.sh ./
28+
RUN chmod +x docker-entrypoint.sh
29+
EXPOSE 3000
30+
ENTRYPOINT ["./docker-entrypoint.sh"]

api/Dockerfile.agent

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Sandbox image for running the Hermes Agent (used when SANDBOX_MODE=docker).
2+
# The orchestrator runs: docker run --rm -i -v <job-dir>:/workspace -w /workspace \
3+
# [-v <HERMES_HOME>:/root/.hermes:ro] <DOCKER_AGENT_IMAGE> hermes -z --yolo ...
4+
#
5+
# This image must contain the `hermes` executable plus a toolchain rich enough for
6+
# the repos you target (git, node, python, build tools, etc.). Add languages/SDKs
7+
# your repositories need.
8+
9+
FROM python:3.12-slim
10+
11+
# Base toolchain. Extend per your repos' needs.
12+
RUN apt-get update && apt-get install -y --no-install-recommends \
13+
git curl ca-certificates build-essential \
14+
&& rm -rf /var/lib/apt/lists/*
15+
16+
# Node 20 (many target repos need it; harmless otherwise).
17+
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
18+
&& apt-get install -y --no-install-recommends nodejs \
19+
&& rm -rf /var/lib/apt/lists/*
20+
21+
# ── Install Hermes Agent ──────────────────────────────────────────────────────
22+
# IMPORTANT: confirm the install command against the current Hermes docs
23+
# (https://github.com/nousresearch/hermes-agent). The line below uses the Python
24+
# package install path; switch to the official installer script if that's preferred.
25+
RUN pip install --no-cache-dir hermes-agent || \
26+
echo "WARNING: adjust this line to the current Hermes install method"
27+
28+
# Provider credentials/config are mounted at runtime via HERMES_HOME -> /root/.hermes.
29+
WORKDIR /workspace
30+
ENTRYPOINT []

api/docker-entrypoint.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/sh
2+
set -e
3+
4+
# Apply any pending migrations against the configured DATABASE_URL before boot.
5+
npx prisma migrate deploy
6+
7+
exec node --enable-source-maps dist/main.js

0 commit comments

Comments
 (0)