Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ chmod 600 ~/.hermes/.env
## Step 5 — Start it

```bash
hermes run &
hermes gateway install
hermes gateway start
hermes gateway status
```

DM your bot. It should reply in seconds.
Expand Down Expand Up @@ -87,9 +89,9 @@ Now try:

## Common first-hour issues

| Symptom | Fix |
|---|---|
| Bot doesn't respond | `journalctl --user -u hermes` — 99% of the time it's a missing env var |
| 401 from Anthropic | Check `ANTHROPIC_API_KEY` has no trailing newline: `cat -A ~/.hermes/.env` |
| "skill not found: /cost-report" | `hermes /reload` after symlinking skills |
| Replies are slow | You're on Anthropic free tier — rate-limited. Upgrade or route to Gemini Flash via the `cost-optimized` template |
| Symptom | Fix |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| Bot doesn't respond | `hermes gateway status` — 99% of the time it's a missing env var |
| 401 from Anthropic | Check `ANTHROPIC_API_KEY` has no trailing newline: `cat -A ~/.hermes/.env` |
| "skill not found: /cost-report" | `hermes /reload` after symlinking skills |
| Replies are slow | You're on Anthropic free tier — rate-limited. Upgrade or route to Gemini Flash via the `cost-optimized` template |
172 changes: 96 additions & 76 deletions docs/reference-architectures/road-warrior.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Reference Architecture: Road Warrior

**Phone drives, disposable cloud boxes do the heavy lifting.** Inspired by [Part 21](../../part21-remote-sandboxes.md). You carry a tiny $5 always-on VPS; it orchestrates Modal / Daytona / Fly sandboxes that spin up on demand for real work.
**Phone drives, remote execution does the heavy lifting.** Inspired by [Part 21](../../part21-remote-sandboxes.md). You carry a tiny always-on VPS for gateways, memory, approvals, and Kanban; real work runs through terminal backends, worktrees, or external integrations.

## Who this is for

Expand All @@ -10,40 +10,42 @@

## Cost

- **Always-on driver box:** $5/mo (Hetzner CX22)
- **On-demand remote compute:** $050/mo (only pay when you're actually running things)
- **LLM:** $2060/mo
- **Always-on driver box:** about $5/mo
- **On-demand remote compute:** $0-50/mo, depending on how often workers run
- **LLM:** $20-60/mo

## Architecture

```
Phone (Telegram) ──→ Driver VPS ($5/mo, always-on)
│ hermes.service
│ remote_sandbox: modal (default)
On-demand sandbox:
Modal (GPU-ish)
Daytona (full dev env)
Fly Machines (persistent)
E2B (Python sandbox)
SSH (your own beast)
```text
Phone (Telegram) ---> Driver VPS (always on)
|
| hermes gateway
| memory, skills, MCP config
| Kanban board
| terminal.backend: ssh/docker/modal/daytona/local
|
v
Execution targets:
SSH dev box or home workstation
Docker/Singularity container
Modal/Daytona backend, when supported
External Vercel/Fly/E2B/CI integration through CLI/MCP/skills
```

Your phone → Telegram → 5¢/mo VPS → spins up a $0.05/hr Modal sandbox → runs Claude Code, pulls the repo, does the work → syncs files back on teardown → pushes PR.
Your phone talks to Hermes on the driver VPS. Hermes creates a worktree or Kanban task, sends shell/file work to the configured terminal backend, runs the coding agent or skill, then reports the diff, test result, PR link, or external artifact back to chat.

## Parts list

- **Hetzner CX22** as the driver ($5/mo)
- **Modal account** (free $30/mo credits) OR **Daytona** OR **Fly Machines** — see [Part 21](../../part21-remote-sandboxes.md)
- **Driver VPS** such as Hetzner CX22, Fly machine, Render, Railway, or a tiny homelab box
- **Execution target:** SSH dev box, Docker/Singularity, or Modal/Daytona if supported by your Hermes build
- **External integrations:** Vercel, Fly Machines, E2B, and CI through vendor CLIs, MCP servers, or custom skills
- **Telegram bot** + your user ID
- **API keys:** Anthropic (for Claude Code inside sandbox), optional Google (for Hermes triage on the driver)
- **API keys:** model providers for Hermes and any coding agents you choose to run

## Install

```bash
# On the driver VPS as root
# On the driver VPS - as root
curl -sSL https://raw.githubusercontent.com/OnlyTerp/hermes-optimization-guide/main/scripts/vps-bootstrap.sh | bash
```

Expand All @@ -54,12 +56,12 @@ Then customize:
version: 1

models:
default: google/gemini-3.1-flash # Cheap + fast for "plan the work" phase
default: google/gemini-3.1-flash # Cheap + fast for triage
providers:
google:
api_key: "${GOOGLE_API_KEY}"
anthropic:
api_key: "${ANTHROPIC_API_KEY}" # Used by sandboxed Claude Code
api_key: "${ANTHROPIC_API_KEY}" # Optional: used by Claude Code or routed model calls

gateways:
cli: { enabled: true }
Expand All @@ -71,84 +73,102 @@ gateways:
allowed_user_ids:
- ${TELEGRAM_OWNER_ID}

# The money section
remote_sandbox:
default_backend: modal # Or daytona / fly / e2b / ssh
backends:
modal:
token_id: "${MODAL_TOKEN_ID}"
token_secret: "${MODAL_TOKEN_SECRET}"
image: "python:3.12-slim"
timeout_idle: 600 # 10m idle → auto-shutdown
ssh: # your home beast, if any
host: "beast.tailnet-xxx.ts.net"
user: "hermes"
identity_file: "~/.ssh/id_ed25519"

# Hermes loads skills from here; these let you orchestrate from Telegram
# The execution section. Use one backend at a time.
terminal:
backend: ssh # local | docker | singularity | modal | daytona | ssh
ssh_host: "beast.tailnet-xxx.ts.net"
ssh_user: "hermes"
ssh_port: 22
ssh_key: "~/.ssh/id_ed25519"

# Hermes loads skills from here; use them to create Kanban cards or call vendor CLIs/MCP tools.
skills:
allowlist:
- pr-review
- release-notes
- cost-report
- remote-run # triggers a sandbox
- remote-run
```

## The workflow
For a fully local-but-contained driver, switch the terminal block to Docker:

```yaml
terminal:
backend: docker
docker_image: nikolaik/python-nodejs:python3.11-nodejs20
cwd: /workspace
docker_mount_cwd_to_workspace: false
```

For Modal or Daytona, use only the backend keys your installed Hermes build exposes. Verify after config edits:

```bash
hermes config check
hermes doctor
```

## The workflow

```text
you: "@bot fix the null-check in auth.ts"
bot: [spinning up modal sandbox…]
bot: cloned acme/app, branch devin-123
bot: claude code: analyzing…
bot: [file diff preview, 3 lines]
bot: created Kanban task #342 on branch wt/auth-null-check
bot: worker claimed an isolated worktree
bot: terminal backend: ssh -> beast.tailnet-xxx.ts.net
bot: codex-worker: running tests...
bot: diff preview: 3 lines changed
Approve? /yes /no /changes
you: /yes
bot: [syncing files back, running tests]
bot: tests green. Pushed PR #342 → https://…
bot: sandbox torn down (ran 4m 12s, $0.014)
bot: tests green. Pushed PR #342 -> https://...
```

## Key wins from Part 21 + PR #8018
The important bit: the source of truth is git. Hermes is not starting a native sandbox and syncing files back on teardown; it is coordinating worktrees, terminal execution, worker logs, tests, and PRs.

- **Bulk tar-pipe sync** — 30s cold start beats 5 minutes of 100× `scp`
- **SIGINT-safe sync-back** — lose signal mid-run, the sandbox still flushes on teardown
- **Hash-only sync** — only changed files come back, not the whole tree
- **Local `git push`** — the driver VPS keeps your authenticated git creds; sandbox never sees them
## Kanban setup

## Skill setup
Create durable work from chat, CLI, or the dashboard:

```bash
# Symlink all the guide skills
for s in /opt/hermes-optimization-guide/skills/*/*/; do
ln -sfn "$s" "/home/hermes/.hermes/skills/$(basename $s)"
done

# Write a tiny remote-run skill (paste into ~/.hermes/skills/remote-run/SKILL.md)
# that wraps `hermes sandbox run --repo acme/app -- claude -p "$@"`
hermes /reload
hermes kanban create "Fix the null-check in auth.ts and open a PR" \
--assignee codex-worker \
--workspace worktree \
--branch wt/auth-null-check

hermes kanban dispatch --max 1
```

A `remote-run` skill should wrap one of these real surfaces:

- create a Kanban card with `--workspace worktree`;
- run a coding agent through a configured terminal backend;
- call a vendor CLI/API such as `vercel`, `flyctl`, or an E2B MCP server;
- return logs, artifact URLs, test status, and PR links.

It should not wrap a sandbox CLI subcommand; that interface is not part of the current CLI.

## Safety rails

- Sandbox = **quarantine profile** (as if it were untrusted input) — Claude Code in the sandbox cannot touch the driver's MCP servers or secrets
- Driver has read-only GitHub PAT (for triage/search)
- The **write** PAT only exists inside the sandbox, short-lived, piped through stdin so it's never on disk
- Terminal backend isolation confines shell/file-tool activity, not every in-process Hermes component. Use [Part 19](../../part19-security-playbook.md) for whole-process containment guidance.
- Keep the driver VPS as the owner of gateways, approvals, memory, and durable Kanban state.
- Pass only the minimum credentials to the remote execution target. Prefer vendor secret stores, short-lived tokens, and PR-based review.
- Use `hermes --worktree` or Kanban `--workspace worktree` for parallel coding work so agents do not collide in one checkout.
- Treat "worker ran" as evidence, not completion. Require tests, review, and a clean branch before calling work done.

## Costs in the wild

Typical month for an active user:
Typical month for an active solo user:

| Line | Cost |
|---|---:|
| CX22 driver | $5 |
| Modal compute (3h/day × 30 days × $0.05/h) | $4.50 |
| Anthropic (Claude Code, routed) | $20–40 |
| Google Gemini Flash (triage) | ~$0.50 |
| **Total** | **~$30–50/mo** |
| Line | Cost |
| -------------------------------- | ----------------: |
| Driver VPS | $5 |
| SSH home box | existing hardware |
| Modal/Daytona/Fly/E2B usage | $0-50 |
| Premium coding-agent model calls | $20-60 |
| Cheap triage model calls | <$5 |
| **Total** | **~$30-120/mo** |

## When to graduate

- You're running 10+ sandbox hours a day → migrate to a persistent Fly Machine + scale up
- You need GPU in the sandbox → Modal A10G is ~$1.10/hr, still cheap for spot usage
- You want *multi-user* → [Small Agency](./small-agency.md)
- You run remote jobs all day -> move the execution target to a persistent dev box or a larger VPS.
- You need GPU bursts -> use Modal, a GPU SSH host, or a CI/vendor runner skill.
- You need vendor-native previews -> call Vercel/Fly/E2B through skills, MCP, or CI instead of pretending they are Hermes-native backends.
- You want multi-user operations -> [Small Agency](./small-agency.md).
7 changes: 4 additions & 3 deletions docs/reference-architectures/small-agency.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ After=network-online.target
Type=simple
User=%i
WorkingDirectory=/home/%i
ExecStart=/usr/local/bin/hermes run
ExecStart=/usr/bin/env hermes gateway run
EnvironmentFile=-/home/%i/.hermes/.env
Environment=PATH=/home/%i/.local/bin:/home/%i/.cargo/bin:/usr/local/bin:/usr/bin:/bin
# ... all the hardening bits from templates/systemd/hermes.service

[Install]
Expand All @@ -80,10 +81,10 @@ systemctl enable --now hermes@alice.service
## Per-client separation

- **`profile:`** in the Hermes config — `quarantine` (untrusted input for a public bot) vs `trusted` (the dev's admin DM)
- **Approval channels** — the dev's DM is the only trusted approval source; client support channels are *never* trusted
- **Approval channels** — the dev's DM is the only trusted approval source; client support channels are _never_ trusted
- **LightRAG dirs** — `~/.hermes/lightrag-<client>/` per client; never mix
- **MCP** — per-client read-only PATs (`GITHUB_PAT_CLIENT_A`, `GITHUB_PAT_CLIENT_B`)
- **Audit log** — append-only JSONL per session, centralized to a single append-only bucket the dev can *read* but not *delete* (makes client reviews easy)
- **Audit log** — append-only JSONL per session, centralized to a single append-only bucket the dev can _read_ but not _delete_ (makes client reviews easy)

## Cost routing at agency scale

Expand Down
64 changes: 16 additions & 48 deletions docs/wizard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -397,54 +397,35 @@ <h1>Hermes Config Wizard</h1>
lines.push(``);
}

// MCP servers
// MCP servers are CLI-managed. The wizard emits comment-only companion
// commands so Hermes owns the persisted MCP registry/config shape.
const anyMcp = Object.values(mcps).some(Boolean);
if (anyMcp) {
lines.push(`mcp_servers:`);
lines.push(`# MCP servers are managed with Hermes CLI commands.`);
lines.push(`# Run selected commands after saving this file:`);
if (mcps.github) {
lines.push(` github:`);
lines.push(` command: npx`);
lines.push(` args: [-y, "@modelcontextprotocol/server-github"]`);
lines.push(` env:`);
lines.push(` GITHUB_PERSONAL_ACCESS_TOKEN: "$\{GITHUB_PAT\}"`);
lines.push(` trust: trusted`);
lines.push(` allow_sampling: false`);
lines.push(`# hermes mcp add github --command npx --env GITHUB_PERSONAL_ACCESS_TOKEN=$\{GITHUB_PAT\} --args -y @modelcontextprotocol/server-github`);
}
if (mcps.postgres) {
lines.push(` postgres:`);
lines.push(` command: npx`);
lines.push(` args: [-y, "@modelcontextprotocol/server-postgres", "$\{DATABASE_URL\}"]`);
lines.push(` trust: trusted`);
lines.push(` allow_sampling: false`);
lines.push(`# hermes mcp add postgres --command npx --args -y @modelcontextprotocol/server-postgres "$\{DATABASE_URL\}"`);
}
if (mcps.cloudflare) {
lines.push(` cloudflare:`);
lines.push(` command: npx`);
lines.push(` args: [-y, "@cloudflare/mcp-server-cloudflare"]`);
lines.push(` env:`);
lines.push(` CLOUDFLARE_API_TOKEN: "$\{CLOUDFLARE_API_TOKEN\}"`);
lines.push(` trust: trusted`);
lines.push(`# hermes mcp add cloudflare --command npx --env CLOUDFLARE_API_TOKEN=$\{CLOUDFLARE_API_TOKEN\} --args -y @cloudflare/mcp-server-cloudflare`);
}
if (mcps.linear) {
lines.push(` linear:`);
lines.push(` url: https://mcp.linear.app/mcp`);
lines.push(` # OAuth is completed by the MCP client on first connection.`);
lines.push(` trust: trusted`);
lines.push(` allow_sampling: false`);
lines.push(`# hermes mcp add linear --url https://mcp.linear.app/mcp --auth oauth`);
}
if (mcps.filesystem) {
lines.push(` filesystem:`);
lines.push(` command: npx`);
lines.push(` args: [-y, "@modelcontextprotocol/server-filesystem", "/home/hermes/scratch"]`);
lines.push(` trust: trusted`);
lines.push(`# hermes mcp add filesystem --command npx --args -y @modelcontextprotocol/server-filesystem /home/hermes/scratch`);
}
if (mcps.mem0) {
lines.push(` mem0:`);
lines.push(` command: npx`);
lines.push(` args: [-y, "@mem0ai/mcp-server-mem0"]`);
lines.push(` env:`);
lines.push(` MEM0_API_KEY: "$\{MEM0_API_KEY\}"`);
lines.push(`# hermes mcp add mem0 --command npx --env MEM0_API_KEY=$\{MEM0_API_KEY\} --args -y @mem0ai/mcp-server-mem0`);
}
lines.push(`# Configure auth/tool filters as needed:`);
lines.push(`# hermes mcp configure NAME`);
lines.push(`# Verify registrations:`);
lines.push(`# hermes mcp test NAME`);
lines.push(`# hermes mcp list`);
lines.push(``);
}

Expand Down Expand Up @@ -473,18 +454,6 @@ <h1>Hermes Config Wizard</h1>
lines.push(` - '169\\.254\\.169\\.254'`);
lines.push(` - 'cat\\s+~?/?\\.?ssh/'`);

// Persona-specific security blocks
if (persona === 'security-hardened') {
lines.push(` mcp:`);
lines.push(` default_trust: untrusted`);
lines.push(` require_allowlist: true`);
lines.push(` allow_sampling: false`);
} else if (anyMcp) {
lines.push(` mcp:`);
lines.push(` default_trust: trusted`);
lines.push(` allow_sampling: false`);
}

if (persona === 'security-hardened' || persona === 'production') {
lines.push(` webhook:`);
lines.push(` max_body_bytes: 1048576`);
Expand All @@ -495,10 +464,9 @@ <h1>Hermes Config Wizard</h1>
lines.push(``);
lines.push(`profiles:`);
lines.push(` quarantine:`);
lines.push(` # Low-trust gateways route here: no memory writes, no MCP, read-only tools`);
lines.push(` # Low-trust gateways route here: no memory writes, read-only tools`);
lines.push(` allow_memory_write: false`);
lines.push(` allow_send: false`);
lines.push(` mcp_servers: []`);
lines.push(` toolsets: [classify]`);
}
lines.push(``);
Expand Down
Loading