— what is chathermes

Introduction

ChatHermes is an autonomous-agent SaaS built on Nous Research's Hermes 4 and Moonshot AI's Kimi K2 Thinking — the official sponsors of the Hermes Agent Creative Hackathon. It pairs streaming multi-model chat with persistent memory, real tools, and agent infrastructure that lets a paid user spin up their own dedicated server. The same code that runs chathermes.com is in this repo.

This documentation covers everything you need to: install ChatHermes, configure it, use the public REST API, build skills, integrate Telegram, deploy a private agent, and self-host the whole thing.

New here? Watch the cinematic /introducing demo first — 17 chapters, ~2 minutes. It shows the entire workflow before you write any code.

— install in 5 minutes

Quickstart

Three install paths. Pick one.

Option 1 — Docker Compose

The recommended path. One command, works on macOS, Linux, Windows / WSL.

git clone https://github.com/ai-co-id/chathermes.git
cd chathermes
./bin/setup.sh           # interactive .env wizard (60s)
docker compose up -d
# → open http://localhost:7000

Option 2 — Hetzner one-click

If you already have a Hetzner Cloud token. Cloud-init bootstraps Bun + Docker + clones repo + writes .env + runs docker compose up.

curl -X POST https://your-self-host.com/api/deploy/hetzner \
  -H "Content-Type: application/json" \
  -d '{
    "token": "hcloud_...",
    "server_type": "cx22",
    "location": "nbg1",
    "llm_keys": { "nous": "..." }
  }'

# Returns: { ok: true, ip, url, ssh_command }

Option 3 — Bun runtime

No Docker. Requires Bun ≥ 1.3 and Node ≥ 22.

# Orchestrator
cd orchestrator
bun install
bun run src/index.ts &

# Web (in another terminal)
cd web
bun install
bun run build
PORT=7000 ORCH_URL=http://127.0.0.1:7010 bun run start

— environment variables

Configuration

ChatHermes reads everything from environment variables. The minimum viable .env:

PUBLIC_BASE_URL=http://localhost:7000
SESSION_SECRET=$(openssl rand -hex 32)
NOUS_API_KEY=hf_...     # OR another LLM provider key — pick one

Everything else degrades gracefully. See the full env var reference below.

Provider keys

You need at least one LLM provider configured. ChatHermes supports:

ProviderEnv varWhat you get
Moonshot AI ★KIMI_API_KEYKimi K2, K2 Thinking — strong reasoning, hackathon co-sponsor
Nous Research ★NOUS_API_KEYHermes 4 (405B + 70B), Hermes 3 — open weights, hackathon host
AnthropicANTHROPIC_API_KEYClaude Sonnet 4.6 — best for code
OpenAIOPENAI_API_KEYGPT-5 — best general
GoogleGEMINI_API_KEYGemini 3.1 Pro — best vision
StepFunSTEPFUN_API_KEYStep 3.5 Flash — fast/cheap

— 14 chat-time tools

Tools

The agent has 14 tools available during chat. All are real APIs — no mockups. Tools are called via <tool_call> XML blocks in the assistant response and parsed by the orchestrator.

NameWhat it doesBackend
web_search(query)5-tier web search fallbackTavily → Brave → DuckDuckGo HTML → Wikipedia → DDG instant
browse(url)Visit URL + extract main contentfetch + article/main extraction
fetch_url(url)Raw HTTP fetchlower-level than browse()
github_repo("owner/name")Repo metadataGitHub REST API
news_search(query)Recent news headlinesGoogle News RSS
weather(location)Live weather + forecastopen-meteo + geocoding
wikipedia(topic)Encyclopedia summaryWikipedia REST API
save_memory(topic, body)Persist a fact across sessionsSQLite, per-user
recall_memory(query)Search saved memoriesSQLite full-text
telegram_send(message)Push to user's Telegram botvia /app/connectors token
run_js(code)Execute JavaScript expressionin-process VM
generate_image(prompt)Create image from textReplicate Flux Schnell
analyze_image(url, question)Vision analysisGemini 2.0 Flash → GPT-4o fallback
dispatch_subagent(task, model)Delegate to a different modelClaude / GPT-5 / Kimi via providers DB
Adding a new tool means: 1) add to TOOLS array in orchestrator/src/tools.ts, 2) add a case in executeTool() for the dispatch logic, 3) update the system prompt in orchestrator/src/index.ts with the rule for when to call it. PRs welcome.

— /app/skills

Skills

Skills are user-toggleable capabilities listed at /app/skills. The system prompt mentions which are active for the user; the agent uses that context to decide when to invoke tools.

Twelve skills ship enabled by default:

  • Research — web_search + browse + cite
  • Code & Build — vibe coding at /app/projects, publish to /p/<slug>
  • Persistent memory — save_memory / recall_memory
  • Scheduler — natural-language cron at /app/schedules
  • Telegram push — telegram_send via connector
  • Subagent dispatch — Claude / GPT-5 / Kimi parallel reasoning
  • Image generation — Flux via Replicate
  • Vision analysis — Gemini 2.0 Flash + GPT-4o fallback
  • Browser — browse() + fetch_url()
  • GitHub recon — github_repo()
  • Weather + News — open-meteo + Google News RSS
  • Run JS — calculations, regex, JSON parsing

— persistent context

Memory

Memory is per-user, scoped via WHERE user_id = ? on every query. The schema:

CREATE TABLE memories (
  id TEXT PRIMARY KEY,
  user_id TEXT NOT NULL,
  topic TEXT NOT NULL,
  body TEXT NOT NULL,
  created_at INTEGER NOT NULL
);
CREATE INDEX idx_memories_user ON memories(user_id, created_at DESC);

Memories are surfaced to the agent two ways:

  1. System prompt context — the latest 20 memories are injected into every chat turn's system message.
  2. recall_memory tool — the agent calls this explicitly when it needs to look something up before answering personal questions.

Users can read, edit, and delete memories at /app/memory. No black box.


— /app/projects, /dev/[id]

Vibe coding

"Build me a landing page for X" → agent generates HTML/CSS/JS that renders live in a sandboxed iframe. Three modes:

ModeWhat it generatesWhen to use
staticsingle index.html (Tailwind CDN allowed)marketing pages, simple UIs
spaReact via esm.sh CDN, single component treeinteractive prototypes
fullstackfrontend + mock API backend in iframedemo apps with state

Per-plan project quotas enforce free-tier limits. POST /api/me/projects returns 402 if the user is over their monthly cap. GET /api/me/projects/quota returns:

{
  "plan": "free",
  "used_this_month": 3,
  "limit_per_month": 5,
  "is_unlimited": false,
  "remaining": 2,
  "pct": 60,
  "lifetime_total": 12,
  "lifetime_published": 4
}

Projects can be published at /p/<slug> for public preview. The orchestrator injects a "Made with ChatHermes" floating badge into published HTML (this is required by the license).


— /api/v1/*

REST API

The public REST API is Bearer-authenticated with API keys created at /app/api-keys. OpenAPI 3.0.3 spec available at /api/openapi.json.

Authentication

curl https://your-self-host.com/api/v1/me \
  -H "Authorization: Bearer ck_..."

Core endpoints

MethodPathWhat it does
GET/api/v1/meUser profile + plan
GET/api/v1/sessionsList chat sessions
POST/api/v1/sessionsCreate a new chat session
POST/api/v1/sessions/:id/chatStream a chat completion (SSE)
GET/api/v1/memoriesList your memories
POST/api/v1/memoriesSave a memory
GET/api/v1/projectsList your vibe-coding projects
GET/api/v1/projects/:idProject detail + history
GET/api/v1/usageCurrent period usage stats

— event delivery

Webhooks

ChatHermes can POST to your endpoints when events fire. Configured at /app/webhooks. Each delivery is HMAC-signed using your webhook secret.

Verifying signatures

import crypto from "node:crypto";

const sig = req.headers["x-chathermes-signature"];
const body = await readRawBody(req);
const expected = crypto
  .createHmac("sha256", WEBHOOK_SECRET)
  .update(body)
  .digest("hex");

if (sig !== expected) {
  res.status(401).send("invalid signature");
}

Events

EventFires when
session.message.createdUser or assistant adds a message
project.publishedVibe-coding project goes live
memory.createdsave_memory tool runs
subscription.changedStripe subscription state changes
agent.readyPrivate Hermes Agent provisioning succeeds

Failed deliveries retry with exponential backoff (3 attempts: ~1s, ~5s, ~30s). After that, the delivery is logged at /admin/email for manual replay.


— Telegram + more

Connectors

Connectors at /app/connectors let users wire ChatHermes into external services. Currently shipping: Telegram.

Telegram

  1. Talk to @BotFather on Telegram → /newbot → save the token.
  2. Open ChatHermes → Connectors → Telegram → paste token → Save.
  3. In Telegram, find your new bot → /start — this binds your Telegram user to your ChatHermes account.
  4. The agent can now call telegram_send(message) tool to push you messages.

— recurring tasks

Schedules

Natural-language cron at /app/schedules. Examples:

  • "Daily briefing at 9am — top 5 stories from Hacker News"
  • "Every 6 hours — check competitor.com/pricing, notify if changed"
  • "Sundays at 8am — summarize what I worked on this week, email it"

The orchestrator parses the natural-language schedule into a cron expression and a target tool chain. When it fires, the agent runs as if the schedule's prompt were sent in a fresh chat.


— per-user dedicated infrastructure

Private Agents

Free-tier users share a single Hermes Agent proxy on the orchestrator's :19002. Paid-tier users can have a dedicated Hetzner server provisioned for them — fully isolated CPU, fully isolated rate limits, fully isolated tool tokens.

Architecture

// resolveHermesEndpoint() in private_agent.ts
// Free user → shared :19002 (this server)
// Paid user + status=ready → user's dedicated server
// Paid user + status=pending/provisioning → falls back to shared

Provisioning flow

  1. Stripe webhook fires customer.subscription.created with active paid status.
  2. Orchestrator marks the user as private_agent_status = pending.
  3. Gated mode (default): an admin opens /admin/private-agents, clicks Provision per user.
  4. Auto mode: set AUTO_PROVISION_PRIVATE_AGENT=true in .env; webhook spawns immediately.
  5. Hetzner Cloud API spawns a fresh server with cloud-init that installs Bun + a per-user proxy + auth token.
  6. Orchestrator polls readiness; when :19002 answers, status flips to ready.
  7. Future hermes-agent requests for that user route to their endpoint.

— one-click + admin fleet

Hetzner deploy

The deploy logic lives in orchestrator/src/deploy.ts. Two surfaces:

Public one-click — /deploy/hetzner

Anyone with a Hetzner token can paste it, pick a server type and region, paste at least one LLM API key, and click Deploy ChatHermes. Cloud-init bootstraps the new server in around 90 seconds.

Admin fleet management — /admin/hetzner

Admins set the Hetzner token once via the UI (stored in system_settings), then manage all servers in their account: list, status, power on/off/reboot, delete. Servers labeled app=chathermes are highlighted as managed.


— production checklist

Self-hosting

Before going live with self-hosted ChatHermes:

  • HTTPS — set up nginx + Certbot. See INSTALL.md Path 3.
  • SESSION_SECRET — generate with openssl rand -hex 32. Don't reuse the example value.
  • ADMIN_EMAILS — set in .env to grant your email admin role on first signup.
  • Backups — set up cron to sqlite3 .backup the SQLite file daily, and rsync off-server.
  • Resend — verify your domain at resend.com/domains for magic links.
  • Stripe — set up products + webhook endpoint. See INSTALL.md "Optional integrations".
  • Hermes Agent native — for the hermes-agent model option, install nous-hermes-agent separately. The orchestrator's shared proxy works without it (proxies to Nous API directly).
  • Required Attribution — don't strip _attribution.ts. The runtime guard refuses to start without it. This is the license.

— full reference

Env vars

Every environment variable. Copy orchestrator/.env.example to orchestrator/.env and fill what you need.

VariableRequired?Purpose
PUBLIC_BASE_URLyesYour install's public URL (used in magic links, OG, webhooks)
SESSION_SECRETyesCookie signing key — openssl rand -hex 32
NODE_ENVnoproduction | development
ADMIN_EMAILSnoComma-separated emails granted admin on signup
DATA_ROOTnoWhere SQLite + tenant volumes live (default ./data)
NOUS_API_KEY1+ neededHermes 4, Hermes 3, Kimi K2 (Nous proxies multiple)
KIMI_API_KEY1+ neededDirect Moonshot Kimi
ANTHROPIC_API_KEY1+ neededClaude Sonnet 4.6
OPENAI_API_KEY1+ neededGPT-5
GEMINI_API_KEY1+ neededGemini 3.1 Pro
STEPFUN_API_KEY1+ neededStep 3.5 Flash
DEFAULT_MODELnoDefault model_id when user has no preference
CHATHERMES_MODEL_RATESnoJSON: {model_id: credits_per_1k_tokens}. Default empty.
CHATHERMES_DEFAULT_RATEnoFallback rate for models not in the rates JSON
RESEND_API_KEYnoIf absent, magic links print to log instead of email
RESEND_FROMnoFrom address — must be verified in Resend
RESEND_REPLY_TOnoReply-to address
RESEND_WEBHOOK_SECRETnoFor verifying inbound delivery events
STRIPE_SECRET_KEYnoIf absent, billing UI shows demo banner
STRIPE_PUBLISHABLE_KEYnoFrontend Stripe.js
STRIPE_WEBHOOK_SECRETnoFor verifying inbound subscription events
STRIPE_PRICE_PROnoStripe price_xxx for Pro plan
STRIPE_PRICE_TEAMnoStripe price_xxx for Team plan
AUTO_PROVISION_PRIVATE_AGENTnofalse (default — admin gates) | true (full auto)
HETZNER_API_TOKENnoAlternative to setting in /admin/hetzner UI
HERMES_PROXY_PORTnoDefault 19002
HERMES_UPSTREAM_MODELnoWhat the shared proxy forwards to (default Hermes-4-405B)
HERMES_RATE_LIMITnoRequests/min per IP on the shared proxy (default 60)
REPLICATE_API_TOKENnoImage generation tool
TAVILY_API_KEYnoWeb search tier 1 (best quality)
BRAVE_API_KEYnoWeb search tier 2

— AGPL-3.0 + Required Attribution

License

ChatHermes is licensed under the ChatHermes Open Source License v1.0 — AGPL-3.0 with a Required Attribution Addendum and Trademark Reservation.

You can: use commercially, modify, redistribute, sell hosting (with your own brand), audit every line.
You cannot: strip the visible "Powered by ChatHermes" link, remove the X-Powered-By header, use the name "ChatHermes" or our mascot for your fork, modify _attribution.ts identifying constants.

The runtime guard in orchestrator/src/_attribution.ts refuses to start the orchestrator if the attribution module is tampered. See /opensource for full terms.


ChatHermes Docs · v1.0 · by @kwkuh & @supercryptolordEdit on GitHub