lciamp / project-hudson · deployment runbook

Project Hudson on a Mac mini

Install and set up the full stack: Hermes, the always-on boss agent you message from your phone, dispatching into Claude Code sessions where 17 specialist subagents and 60 skills do the work, backed by a Postgres + pgvector memory store.

What you're building

Project Hudson is a multi-agent AI team. The boss is Hermes — an always-on daemon on the Mac mini that receives your Telegram messages and can run on any of 40+ model providers. For project work it acts as a thin relay: each task is dispatched into a headless Claude Code session inside the project-hudson repo, where the orchestration playbook (CLAUDE.md) routes work to the right specialist. Outcomes land in a long-term vector memory store (Postgres + pgvector) and results relay back to your phone.

  • 8 engineering
  • 4 reliability & ops
  • 5 business
  • 57 skills

There are two pieces, installed in order: Hermes (the boss gateway), then this repo (the subagents, skills, and memory database). A third short step wires them together.

Model layering

The model you give Hermes powers only the boss layer — chat, routing, Hermes's own tools. The 17 specialists always run on Claude, via the claude login you set up in step 1. Hermes-on-Qwen (or OpenAI, DeepSeek, a local Ollama model…) works fine as the boss.

Prerequisites

  • A Mac mini (or any machine that stays on) for the always-on boss. Any Mac works for a local-only setup.
  • Homebrew and Git.
  • One database option for the memory store: Docker, native Homebrew Postgres (the setup script installs it for you — recommended on the mini), or a free Neon serverless account.
  • A Telegram account, for the bot you'll message.
  • A Claude login for Claude Code. If you want Hermes itself on Anthropic without per-token billing, that requires a Claude Max subscription (Pro is not eligible).

Install Hermes — the boss agent

Base tooling

Node runs the Claude Code CLI; uv runs the memory-database MCP server.

Terminal
brew install node python ripgrep ffmpeg git uv
npm install -g @anthropic-ai/claude-code
claude   # log in once; verify with: claude doctor

Hermes itself

The installer is curl-pipe-bash — download it and read it before running it.

Terminal
curl -fsSL https://hermes-agent.nousresearch.com/install.sh -o /tmp/hermes-install.sh
less /tmp/hermes-install.sh   # inspect before running
bash /tmp/hermes-install.sh
source ~/.zshrc

Configuration lives in ~/.hermes/config.yaml; secrets in ~/.hermes/.env. Pin the Hermes version — don't let an unattended machine auto-update.

Pick a model for the boss

Run hermes model to configure and choose. Any of these work:

  • Anthropichermes auth add anthropic --type oauth reuses a Claude Max subscription (no per-token billing; Pro is not eligible), or set ANTHROPIC_API_KEY in ~/.hermes/.env.
  • QwenDASHSCOPE_API_KEY in ~/.hermes/.env, or Qwen Portal OAuth.
  • OpenAI, OpenRouter, DeepSeek, AWS Bedrock — via API keys.
  • Local models — Ollama, vLLM, or LM Studio (any OpenAI-compatible endpoint).

Inside a chat, /model provider:model switches models without losing history or memory.

Create the Telegram bot

This is the abbreviated version — the full walkthrough (signing up, finding your user ID, admitting other people) is the Telegram setup guide.

  1. Message @BotFather/newbot → copy the token.
  2. Get your numeric Telegram user ID from @userinfobot.
  3. Add both to ~/.hermes/.env:
~/.hermes/.env
TELEGRAM_BOT_TOKEN=<token from BotFather>
TELEGRAM_ALLOWED_USERS=<your numeric user ID>
Never run allow-all

Always set the user allowlist. Never enable GATEWAY_ALLOW_ALL_USERS=true — a documented fail-open incident exposed gateways to unauthenticated Telegram DMs. Admit new users with Hermes pairing codes (they expire after an hour) instead.

Start the gateway

Terminal
hermes gateway setup     # wizard
hermes gateway start
# logs: ~/.hermes/logs/gateway.log

Install the subagents & memory DB

Cloning is the install — the 17 subagents in .claude/agents/ and the 60 skills in .claude/skills/ register with Claude Code automatically.

Terminal
git clone https://github.com/lciamp/project-hudson.git ~/repos/project-hudson
cd ~/repos/project-hudson

Start the memory database — pick one

Option B · Native Postgres via Homebrew

Recommended for Mac mini

No Docker Desktop needed — best for an always-on machine. Installs postgresql@18 + pgvector, starts the service, creates the project_hudson role and database, and applies db/init/01-schema.sql. Idempotent; refuses to run if something else already holds port 5432.

Terminal
scripts/setup-native-postgres.sh

Option A · Docker

Starts project-hudson-postgres (pgvector/pgvector:pg18) on localhost:5432 and applies the schema — a memories table with vector(1536) embeddings, HNSW index, and hybrid full-text search. Init scripts only run on an empty volume; later schema changes need migrations.

Terminal
docker compose up -d

Option C · Neon serverless Postgres

Cloud-reachable

No local database at all — good for POCs, and the only option cloud-hosted agents can reach. Create a free project at neon.tech, apply the schema over its connection string, then add a neon entry to .mcp.json: "neon": { "type": "http", "url": "https://mcp.neon.tech/mcp" } (authenticates via browser OAuth — no key in the file). Keep the connection string in an env var; never commit it.

Terminal
psql "$NEON_DATABASE_URL" -f db/init/01-schema.sql

Approve the MCP server

Run claude once inside the repo and approve the project's MCP server from .mcp.json — the postgres server (runs crystaldba/postgres-mcp via uvx, pinned to postgres-mcp==0.3.0, against the local database) for options A/B, or the neon server for option C. This also caches the workspace-trust prompt, so Hermes-spawned headless sessions run friction-free.

Connect Hermes to the subagents

Tell the bot this in your first conversation, so it lands in Hermes's memory:

Send to your bot

For any project-hudson work, use the claude-code skill with workdir /Users/<you>/repos/project-hudson, print mode, and resume sessions rather than starting new ones for follow-ups. Don't answer project-hudson questions from your own tools.

This makes Hermes a thin relay: all project work runs inside Claude Code, where the boss playbook and its gates live — security review after auth/input/infra changes, chaos validation for failover claims, approval boundaries — instead of Hermes improvising with its own tools.

Make it always-on

Register the gateway as a launchd LaunchAgent and keep the mini awake:

Terminal
hermes gateway install              # registers login auto-start (launchd)
sudo pmset -a sleep 0 disksleep 0   # prevent sleep
Reboot test

After setup, reboot the mini and confirm the bot responds before trusting it unattended.

Known launchd quirks on macOS

  • Exit-78 config errors can leave launchd stuck.
  • Restart race: use hermes gateway stop && sleep 5 && hermes gateway start — avoid a bare restart.
  • macOS 26+: silent fallback to a detached process — no auto-start or crash restart. Check hermes gateway status after reboots.

Verify

End-to-end, from your phone:

  1. Ask the bot: "in the project-hudson repo, list your agents" — expect the 17 specialists.
  2. Send a routed task: "find 5 product ideas in the dev-tools niche" — it should dispatch into Claude Code and come back via product-researcher + the market-niche-scan skill.

Security hardening

Authorization strategy, in order of preference

  1. Pre-authorize in the repo — commit allow rules to .claude/settings.json so every Hermes session inherits them and remote headless sessions never hit permission prompts.
  2. Per-call flags--allowedTools 'Read,Edit,Bash(git status:*),Bash(git diff:*)', --permission-mode acceptEdits. Scope Bash to exact subcommands — never bare Bash(git *): git's -c core.pager=… and aliases enable arbitrary shell execution.
  3. Guardrails — always include --max-turns and --max-budget-usd.
Avoid

Never use --dangerously-skip-permissions on an internet-reachable agent — the boundaries in AGENTS.md only hold if enforcement is on.

Hardening checklist

  • Keep the bot token out of the repo; revoke via BotFather immediately if it leaks.
  • Don't add the bot to public groups — every readable message is injection surface for an agent with shell access.
  • Run the gateway under a dedicated macOS user account.
  • Rotate the dev database credentials (project_hudson:project_hudson_dev) outside local development, injected from a secrets manager — never hardcoded.
  • Ignore third-party "auth bypass" add-ons — they're credential-theft vectors. Use official Max OAuth.

The full walkthrough — launchd caveats, the permission model for headless sessions, and hardening detail — is in docs/hermes-setup.md.

Troubleshooting

SymptomFix
Silent after rebootlaunchd fell back to a detached process — check hermes gateway status, restart manually.
Silent after gateway restartRestart race — use stop && sleep 5 && start.
Two machines fighting over the botOne token per gateway — stop the competing instance.
--resume failsResume must run from the original workdir.
Session ignores CLAUDE.mdCheck the skill's workdir — it must be the repo root.
Permission blocked mid-taskAdd the tool to .claude/settings.json or --allowedTools.

Quick reference

CommandWhat it does
claude doctorVerify the Claude Code install and login.
hermes modelConfigure and switch the boss model.
hermes gateway setup / start / installInitialize, run, and auto-start the Telegram gateway.
/reload-pluginsIn Claude Code — pick up agent or skill edits (or start a new session).
python3 scripts/sync-copilot-agents.pyRegenerate the Copilot agent files after editing agents (--check for CI drift).
python3 scripts/gen-skills-readme.pyRegenerate the skills index in .claude/skills/README.md after adding or re-describing a skill (--check for CI drift).
scripts/pack-for-move.shBundle repo + boss memory + DB dump into a zip for migrating machines.

Using the team without Hermes also works: run claude in the repo and the session itself acts as the boss per CLAUDE.md. The same agents and skills load in GitHub Copilot (VS Code, Copilot CLI, and the cloud agent — which can only reach a Neon database, not a local one).