diff --git a/.agents/HERMES.md b/.agents/NOMOS.md similarity index 88% rename from .agents/HERMES.md rename to .agents/NOMOS.md index 73b73d0..3da0ceb 100644 --- a/.agents/HERMES.md +++ b/.agents/NOMOS.md @@ -1,4 +1,4 @@ -# HERMES.md — Agent persona for homelab clients +# NOMOS.md — Agent persona for homelab clients This file is the canonical agent persona for **all** AI agents running on machines in the **hubris** homelab. It prescribes behaviour, token-efficiency @@ -32,8 +32,8 @@ approval flow, ontology). | Agent | Loading mechanism | |-------|------------------| -| **Hermes** | `tools/setup-hermes-soul.sh` (auto-setup) → provisions `~/.hermes/SOUL.md` from this file | -| **Goose** | `.goosehints` symlink at `~/.config/goose/.goosehints` → `/opt/homelab-context/HERMES.md` | +| **Nomos** | `tools/setup-nomos-soul.sh` (auto-setup) → provisions `~/.nomos/SOUL.md` from this file | +| **Goose** | `.goosehints` symlink at `~/.config/goose/.goosehints` → `/opt/homelab-context/NOMOS.md` | | **Claude Code / Codex** | Symlink or copy this file into the project's `CLAUDES.md` / `.claude` instructions | **Do not edit SOUL.md or .goosehints directly.** Edit this file in the @@ -87,10 +87,10 @@ Caveman templates live at `~/templates/`: ls ~/bin/caveman_wrapper.sh && echo "caveman ready" ``` -## Important note for Hermes agents +## Important note for Nomos agents -If you are reading this as a Hermes agent, your SOUL.md was auto-provisioned -by `tools/setup-hermes-soul.sh`. This file is the canonical original — you +If you are reading this as a Nomos agent, your SOUL.md was auto-provisioned +by `tools/setup-nomos-soul.sh`. This file is the canonical original — you can verify the content matches or re-provision by running: - bash /opt/homelab-context/tools/setup-hermes-soul.sh \ No newline at end of file + bash /opt/homelab-context/tools/setup-nomos-soul.sh diff --git a/.agents/OIKOS.md b/.agents/OIKOS.md index 12ba0d9..5117268 100644 --- a/.agents/OIKOS.md +++ b/.agents/OIKOS.md @@ -137,13 +137,13 @@ in the Go binary. - Go packages: `internal/scheduler/`, `internal/actuator/`, `internal/learning/`, `internal/notifier/`, `internal/policy/`. -**Phase 4 — Agent / Hermes (DONE):** -- Standalone Hermes MCP client binary (`cmd/hermes`) with gateway mode +**Phase 4 — Agent / Nomos (DONE):** +- Standalone Nomos MCP client binary (`cmd/nomos`) with gateway mode (:8092). Structured queries + natural-language routing to 15 MCP tools. Agent activity logging on every tool call. No SSH keys. -- `hermes/` directory with config, SOUL.md, homelab-ops skill. -- Hermes Docker service in `docker-compose.yml` (profile: full). -- Go packages: `cmd/hermes/`, `compose/hermes/`. +- `nomos/` directory with config, SOUL.md, homelab-ops skill. +- Nomos Docker service in `docker-compose.yml` (profile: full). +- Go packages: `cmd/nomos/`, `compose/nomos/`. **Phase 5 — Secrets / Infisical (DONE):** - `internal/secrets/`: backend abstraction (Manager) with primary @@ -159,7 +159,7 @@ in the Go binary. lint, test, docker build). - Deploy: `scripts/deploy.sh` (git pull → docker build → compose up → health check), SHA-tagged images, rolling restart. -- Caddy config: `compose/caddy/Caddyfile.oikos` (oikos/mcp/hermes → +- Caddy config: `compose/caddy/Caddyfile.oikos` (oikos/mcp/nomos → mac-mini mesh :8090/:8092). - Watchdog: `scripts/watchdog.sh` (2min cron, Matrix alert on failure). - Verification: `scripts/verify-phase6.sh` (14/14 checks pass). @@ -168,7 +168,7 @@ in the Go binary. **Current deployment:** - **Production**: Docker stack on mac-mini (`--profile full`: postgres, api, - scheduler, notifier, hermes). Deployed 2026-07-07 with full knowledge seed. + scheduler, notifier, nomos). Deployed 2026-07-07 with full knowledge seed. The Python MCP server and secrets-issuance on apps/105 have been stopped (see `scripts/cutover-checklist.md`). diff --git a/.agents/dev/CONTRIBUTING.md b/.agents/dev/CONTRIBUTING.md index d2617d5..c199adb 100644 --- a/.agents/dev/CONTRIBUTING.md +++ b/.agents/dev/CONTRIBUTING.md @@ -9,7 +9,7 @@ see [CONTRIBUTING.md](../../CONTRIBUTING.md) for a human-friendly version. ``` cmd/oikos/main.go Entry point. Subcommands: api, scheduler, notifier, migrate, seed, export, secret, all -cmd/hermes/main.go Hermes MCP client gateway (standalone binary) +cmd/nomos/main.go Nomos MCP client gateway (standalone binary, formerly Hermes) internal/httpapi/ REST + MCP server. Chi router. OpenAPI-generated types from internal/httpapi/gen/api.gen.go. Strict server in impl.go. internal/mcp/ MCP tool implementations (get_entity, search_knowledge, etc.) @@ -31,10 +31,10 @@ api/codegen.yaml oapi-codegen config → generates internal/httpapi/g migrations/ Forward-only SQL. Format: NNN_name.up.sql. No down migrations. seeds/ Bootstrap YAML. ontology.yaml, inventory.yaml, policy.yaml, knowledge.yaml. Regenerated from DB via oikos export. -compose/ Dockerfiles. oikos/ (multi-stage), hermes/ (distroless). +compose/ Dockerfiles. oikos/ (multi-stage), nomos/ (distroless). Caddy config at compose/caddy/Caddyfile.oikos. scripts/ Deploy, rollback, watchdog, verification, cutover checklist. -hermes/ Hermes config.yaml, SOUL.md, skills. +nomos/ Nomos config.yaml, SOUL.md, skills. .agents/ Agent instruction files, domains, shared conventions, skills. plans/ Design documents. active/ + done/. docs/adr/ Architecture decision records. Numbered, prefix-sorted. diff --git a/.agents/operations/agent-enrollment.md b/.agents/operations/agent-enrollment.md index 6377aa3..dce3790 100644 --- a/.agents/operations/agent-enrollment.md +++ b/.agents/operations/agent-enrollment.md @@ -6,8 +6,8 @@ this repo that auto-syncs every 5 min, a per-client age key for SOPS decryption, the `homelab` CLI, and an MCP endpoint in Claude Code's config. > Onboarding a Nous-Hermes-powered Goose agent on top of standard enrollment? -> See [hermes-agent.md](hermes-agent.md). It uses the same `bootstrap.sh` -> with an additional `--with-hermes` flag. +> See [nomos-agent.md](nomos-agent.md). It uses the same `bootstrap.sh` +> with an additional `--with-nomos` flag. Architecture in [project_homelab_context_plan](https://… memory link); the operational reference is here. @@ -354,9 +354,9 @@ Added a new "Post-bootstrap: SSH reachability" section covering SSH key generation, pubkey publication, deployment to hosts, SSH config generation, and LAN IP registration. New workstations enrolled via this doc will automatically join the universal SSH mesh. +### 2026-05-31 — cross-link to nomos-agent.md -### 2026-05-31 — cross-link to hermes-agent.md -Added a sibling page covering Nous-Hermes-on-Goose enrollment ([hermes-agent.md](hermes-agent.md)) and noted it at the top of this page. The Hermes flow extends `bootstrap.sh` with `--with-hermes` and `homelab client add` with the same flag; it does not change the underlying enrollment steps documented here. +Added a sibling page covering Nous-Hermes-on-Goose enrollment ([nomos-agent.md](nomos-agent.md)) and noted it at the top of this page. The Nomos flow extends `bootstrap.sh` with `--with-nomos` and `homelab client add` with the same flag; it does not change the underlying enrollment steps documented here. ### 2026-05-21 — netbird-ssh JWT issuer + username + LAN-fallback troubleshooting rows Added three rows to the troubleshooting table covering issues surfaced during the netbird vanilla migration: (1) post-migration SSH JWT validator cache stuck on old Dex issuer (full `systemctl stop/start` required, not `restart`), (2) `user not found` from netbird-ssh's local-username default (use explicit `root@`), and (3) homelab CLI's LAN→netbird-FQDN fallback for off-LAN operators. Companion code change: per-host `ssh.user` field in `inventory.yaml` + `homelab` CLI's `ssh_target()` helper. diff --git a/.agents/operations/commands.md b/.agents/operations/commands.md index 7690489..fec5ff7 100644 --- a/.agents/operations/commands.md +++ b/.agents/operations/commands.md @@ -14,7 +14,7 @@ Run from the [hubris host](../../archive/knowledge/hosts/hubris.md) as root. Whe | `pvesm status` | Storage pools status | | `pvesh get /nodes --output-format json` | Node summary as JSON | | `pvesh get /nodes/hubris/lxc//status/current` | Live container status | -| `pvesh get /cluster/resources --type vm --output-format json` | Bulk per-LXC CPU/mem/disk (used by the `homelab-health-watchdog` Hermes cron — see [monitoring](../../archive/knowledge/infrastructure/monitoring.md); the old `claudio-monitor` this once fed is deprecated) | +| `pvesh get /cluster/resources --type vm --output-format json` | Bulk per-LXC CPU/mem/disk (used by the `homelab-health-watchdog` Nomos cron — see [monitoring](../../archive/knowledge/infrastructure/monitoring.md); the old `claudio-monitor` this once fed is deprecated) | | `pveversion` | PVE version | | `journalctl -u pve-cluster -n 100` | PVE service logs | @@ -77,7 +77,7 @@ See [OIKOS.md](../OIKOS.md) for the operating model. Quick reference: | `homelab change preflight ` | Dry-run report before mutating: risk class, current health, config repo, verification command | | `homelab decide ` | Decision classifier: risk × blast radius × confidence → auto-act or escalate | | `homelab signal list\|raise\|ack\|resolve\|mute` | The attention layer — pending updates, thresholds, drift, anything needing attention | -| `homelab approval request\|list\|reply\|check` | Escalate-route grants (Matrix-delivered via Hermes, or the Oikos Console's `/approvals` page) | +| `homelab approval request\|list\|reply\|check` | Escalate-route grants (Matrix-delivered via Nomos, or the Oikos Console's `/approvals` page) | | `homelab restart [--approval-id ]` | `--approval-id` is required whenever the service's risk class needs approval (e.g. `caddy`, `dns`) — refuses mechanically without a valid grant | Oikos Console (read-mostly dashboard): `oikos.hubris.network` once deployed — see [oikos/console/deploy/README.md](../../archive/oikos-cards/). diff --git a/.agents/operations/hermes-agent.md b/.agents/operations/nomos-agent.md similarity index 72% rename from .agents/operations/hermes-agent.md rename to .agents/operations/nomos-agent.md index 2a3598e..f3ac066 100644 --- a/.agents/operations/hermes-agent.md +++ b/.agents/operations/nomos-agent.md @@ -1,4 +1,4 @@ -# Hermes agent — Nous-Hermes-powered Goose sessions on a homelab client +# Nomos agent — LLM-powered terminal sessions on a homelab client Onboards [Nous Research's Hermes](https://nousresearch.com/) (a fine-tuned Llama variant) as a working terminal agent on a homelab client. Builds on top @@ -8,13 +8,13 @@ of standard client enrollment (see [agent-enrollment.md](agent-enrollment.md)) The agent runs as a [Goose](https://goose-docs.ai/) session. Goose provides: - The chat loop, multi-turn history, and streaming -- The OpenRouter provider that routes to Nous Hermes +- The OpenRouter provider that routes to the configured LLM - The built-in `developer` extension (shell + file editor — same surface Claude Code has) - A remote MCP extension pointed at `mcp.hubris.network` for read-only homelab context (`list_lxcs`, `tail_log`, `search_docs`, etc.) -The persona is `/opt/homelab-context/HERMES.md`, symlinked as Goose's global +The persona is `/opt/homelab-context/NOMOS.md`, symlinked as Goose's global `.goosehints` so it's injected into the system prompt on every session. ## Prerequisites @@ -23,7 +23,7 @@ The persona is `/opt/homelab-context/HERMES.md`, symlinked as Goose's global | --- | --- | | Standard enrollment complete (`homelab whoami` works) | [agent-enrollment.md](agent-enrollment.md) | | `secrets/openrouter-api-key.yaml` exists with a real `sk-or-...` value | See "Seeding the OpenRouter key" below | -| The host's `age_pubkey` is on the openrouter-api-key.yaml sops rule | `homelab client add --finalize-pubkey --with-hermes` | +| The host's `age_pubkey` is on the openrouter-api-key.yaml sops rule | `homelab client add --finalize-pubkey --with-nomos` | ## Onboarding flow @@ -33,37 +33,37 @@ homelab client add new-machine # 2. Join new-machine to Netbird (setup-key or OIDC). -# 3. On new-machine: bootstrap with --with-hermes. +# 3. On new-machine: bootstrap with --with-nomos. TOKEN=... # gitea PAT, read:repository curl -fsSL -u "dtoro:$TOKEN" \ https://git.hubris.network/dtoro/oikos/raw/branch/main/bootstrap.sh \ -o /tmp/bootstrap.sh -sudo HOMELAB_GITEA_TOKEN=$TOKEN bash /tmp/bootstrap.sh --with-mcp --with-hermes +sudo HOMELAB_GITEA_TOKEN=$TOKEN bash /tmp/bootstrap.sh --with-mcp --with-nomos -# 4. Back on hubris: finalize the age pubkey AND grant the Hermes secret. +# 4. Back on hubris: finalize the age pubkey AND grant the Nomos secret. homelab client add new-machine \ --finalize-pubkey age1... \ - --with-hermes + --with-nomos # 5. Wait ≤5 min for sync, then on new-machine: -hermes "what LXCs are running?" +nomos "what LXCs are running?" ``` -The bootstrap `--with-hermes` flag does five things, all idempotent: +The bootstrap `--with-nomos` flag does five things, all idempotent: 1. Downloads the latest Goose binary into the operator's `~/.local/bin/goose` (upstream installer) and symlinks `/usr/local/bin/goose` to it. -2. Symlinks `/opt/homelab-context/bin/hermes` → `/usr/local/bin/hermes`. -3. Symlinks `/opt/homelab-context/HERMES.md` → `/root/HERMES.md` (Linux) or - `/etc/HERMES.md` (macOS) for `cat`-as-operator convenience. +2. Symlinks `/opt/homelab-context/bin/nomos` → `/usr/local/bin/nomos`. +3. Symlinks `/opt/homelab-context/NOMOS.md` → `/root/NOMOS.md` (Linux) or + `/etc/NOMOS.md` (macOS) for `cat`-as-operator convenience. 4. Drops `~/.config/goose/config.yaml` pinning the provider, model, and extensions (preserves any keys the operator added by hand). -5. Symlinks `~/.config/goose/.goosehints` → HERMES.md, so the persona is +5. Symlinks `~/.config/goose/.goosehints` → NOMOS.md, so the persona is injected as the system prompt on every session. ## Seeding the OpenRouter key -The first time anyone enrolls with `--with-hermes`, the encrypted file +The first time anyone enrolls with `--with-nomos`, the encrypted file `secrets/openrouter-api-key.yaml` contains a placeholder. On hubris (or any existing recipient): @@ -75,19 +75,19 @@ git -C /opt/homelab-context commit -m 'openrouter-api-key: seed real key' git -C /opt/homelab-context push ``` -Until this step happens, `hermes …` exits with `openrouter-api-key.yaml still +Until this step happens, `nomos …` exits with `openrouter-api-key.yaml still contains the placeholder`. Subsequent enrollees get the real key automatically -via `--with-hermes` (which adds them as a sops recipient on +via `--with-nomos` (which adds them as a sops recipient on `secrets/openrouter-api-key.yaml`). ## Granting the OpenRouter key to an already-enrolled host -If a host was enrolled without `--with-hermes` and you want to add it later: +If a host was enrolled without `--with-nomos` and you want to add it later: ```bash # On hubris: PUBKEY=$(homelab whoami --hostname | grep age_pubkey | awk '{print $2}') -homelab client add --finalize-pubkey "$PUBKEY" --with-hermes +homelab client add --finalize-pubkey "$PUBKEY" --with-nomos ``` `--finalize-pubkey` is required by the existing flow even when the pubkey is @@ -101,12 +101,12 @@ re-run; only the secret recipient list changed. ```bash homelab whoami # standard enrollment OK homelab secret openrouter-api-key | head -c 8 # decrypts (prints `api_key:`) -which goose && which hermes # binaries present +which goose && which nomos # binaries present goose info -v # provider/model wiring sane -hermes "what LXCs are running?" # interactive Goose session +nomos "what LXCs are running?" # interactive Goose session # Non-interactive smoke test: -echo "List the homelab MCP tools you have available" | hermes +echo "List the homelab MCP tools you have available" | nomos ``` ## Configuration @@ -135,9 +135,9 @@ extensions: Override via env on a single bootstrap run: ```bash -HOMELAB_HERMES_MODEL=nousresearch/hermes-3-llama-3.1-405b \ -HOMELAB_HERMES_MCP_URI=https://mcp.hubris.network/mcp \ - sudo bash /tmp/bootstrap.sh --with-hermes +HOMELAB_NOMOS_MODEL=nousresearch/hermes-3-llama-3.1-405b \ +HOMELAB_NOMOS_MCP_URI=https://mcp.hubris.network/mcp \ + sudo bash /tmp/bootstrap.sh --with-nomos ``` Any keys you add by hand (e.g. `GOOSE_TEMPERATURE`, extra `extensions.*`) are @@ -156,22 +156,22 @@ every tool call, use `approve`. See | Symptom | Cause | Fix | | --- | --- | --- | -| `hermes: could not decrypt secrets/openrouter-api-key.yaml` | Host isn't a recipient on the sops rule | `homelab client add --finalize-pubkey --with-hermes` from hubris | -| `hermes: openrouter-api-key.yaml still contains the placeholder` | No real key has been seeded yet | See "Seeding the OpenRouter key" above | -| Goose hangs on first `hermes` invocation with no output | Goose's interactive `configure` ran on first launch and is awaiting input | Re-run; the installer is supposed to skip it (CONFIGURE=false). If it persists, run `goose configure` once manually in a real terminal to commit the config. | +| `nomos: could not decrypt secrets/openrouter-api-key.yaml` | Host isn't a recipient on the sops rule | `homelab client add --finalize-pubkey --with-nomos` from hubris | +| `nomos: openrouter-api-key.yaml still contains the placeholder` | No real key has been seeded yet | See "Seeding the OpenRouter key" above | +| Goose hangs on first `nomos` invocation with no output | Goose's interactive `configure` ran on first launch and is awaiting input | Re-run; the installer is supposed to skip it (CONFIGURE=false). If it persists, run `goose configure` once manually in a real terminal to commit the config. | | `homelab` extension fails to connect / no MCP tools listed | MCP server upgraded in Go rewrite (`internal/mcp/server.go`, Streamable HTTP via official MCP SDK). Old FastMCP SSE transport is deprecated. | Run `docker compose --profile full up` on mac-mini, or wait for the production cutover from apps/105. | -| `goose: command not found` after bootstrap | Upstream installer dropped binary in `~/.local/bin/` but `/usr/local/bin/goose` symlink didn't land | Re-run bootstrap with `--with-hermes`; the symlink step is at the end of the install block. If still missing, `ln -sfn ~/.local/bin/goose /usr/local/bin/goose` manually. | +| `goose: command not found` after bootstrap | Upstream installer dropped binary in `~/.local/bin/` but `/usr/local/bin/goose` symlink didn't land | Re-run bootstrap with `--with-nomos`; the symlink step is at the end of the install block. If still missing, `ln -sfn ~/.local/bin/goose /usr/local/bin/goose` manually. | | Tool calls hit OpenRouter rate limits | One shared key across many hosts | Future: per-host keys; for now, see the rate-limits guide referenced in `goose info -v`. | ## Cross-references - [agent-enrollment.md](agent-enrollment.md) — base client onboarding the - Hermes flow assumes is done. -- [`HERMES.md`](../HERMES.md) — the persona the Hermes agent reads on every + Nomos flow assumes is done. +- [`NOMOS.md`](../NOMOS.md) — the persona the Nomos agent reads on every session start (via `~/.config/goose/.goosehints`). -- [`bin/hermes`](../../bin/hermes) — the wrapper that decrypts the OpenRouter key +- [`bin/nomos`](../../bin/nomos) — the wrapper that decrypts the OpenRouter key and execs `goose session`. -- [`bootstrap.sh`](../../bootstrap.sh) — the `--with-hermes` flag's install block. +- [`bootstrap.sh`](../../bootstrap.sh) — the `--with-nomos` flag's install block. ## Follow-ups @@ -182,7 +182,7 @@ every tool call, use `approve`. See that's changed, the `homelab` MCP extension in Goose will fail to connect. The developer extension (shell + edit) covers most ops without it; this is a polish item, not a blocker. -2. **Per-host OpenRouter keys** for billing attribution. Today all Hermes +2. **Per-host OpenRouter keys** for billing attribution. Today all Nomos hosts share one key. 3. **Pin the model version** rather than tracking `nousresearch/hermes-4-405b` directly — OpenRouter periodically rotates the underlying weights. @@ -204,7 +204,7 @@ templating + `~/bin/caveman_wrapper.sh` + `~/templates/*.txt` for token- efficient CLI output. Replaces raw `git pull` in launchd/systemd timers. Also created `tools/caveman/` with the wrapper script, JS renderer, and templates — the canonical source for all agent hosts. -Captures the Hermes-on-Goose onboarding flow added in the same commit as -`bootstrap.sh --with-hermes`, `bin/hermes`, the sops rule for -`secrets/openrouter-api-key.yaml`, and the `homelab client add --with-hermes` +Captures the Nomos-on-Goose onboarding flow added in the same commit as +`bootstrap.sh --with-nomos`, `bin/nomos`, the sops rule for +`secrets/openrouter-api-key.yaml`, and the `homelab client add --with-nomos` extension. MCP streamable_http migration is queued as follow-up #1. diff --git a/.agents/shared/caveman.md b/.agents/shared/caveman.md index a6225d4..309bfe9 100644 --- a/.agents/shared/caveman.md +++ b/.agents/shared/caveman.md @@ -30,4 +30,4 @@ Code/commits/PRs: write normal. "stop caveman" or "normal mode": revert. Level p --- Source: https://github.com/JuliusBrussee/caveman -Copy to `~/.hermes/skills/` for Hermes Agent, or `~/.claude/projects//SKILL.md` for Claude Code. +Copy to `~/.nomos/skills/` for Nomos agent, or `~/.claude/projects//SKILL.md` for Claude Code. diff --git a/.agents/skills/client-enrollment/SKILL.md b/.agents/skills/client-enrollment/SKILL.md index 02b11c6..3844999 100644 --- a/.agents/skills/client-enrollment/SKILL.md +++ b/.agents/skills/client-enrollment/SKILL.md @@ -26,7 +26,7 @@ the full walkthrough; this runbook is the risk/lifecycle framing. routed `192.168.8.0/24` Netbird network resource. Skip this step for LAN-only nodes; do it (out-of-band, console or setup key) only for hosts that need independent off-LAN reachability. -3. On the new host: run `bootstrap.sh` (add `--with-hermes` to also +3. On the new host: run `bootstrap.sh` (add `--with-nomos` to also enroll the Hermes agent). This provisions `/etc/age/key.txt`, the sync timer, and prints an age pubkey. 4. Back on an enrolled client: `homelab client add diff --git a/.gitignore b/.gitignore index f56f8ab..5d8e4fe 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,9 @@ __pycache__/ # Regenerated every scheduler run; ephemeral health-probe cache. oikos/state.json -# Compiled binaries (Go rewrite — bin/oikos, bin/hermes) +# Compiled binaries (Go rewrite — bin/oikos, bin/nomos) bin/oikos -bin/hermes +bin/nomos oikos/oikos # Legacy Python oikos (superseded by cmd/oikos Go binary — Phase 1-6 rewrite). diff --git a/.sops.yaml b/.sops.yaml index 7dd12f5..f6cee89 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -86,11 +86,11 @@ creation_rules: age1z62ff2ak9zj5ctcvaxwyyhedwjvlwgm2dkn9nk3wrwk8fkavcpmsqwc2vs - path_regex: archive/secrets-sops-backupopenrouter-api-key\.yaml$ - # OpenRouter API key consumed by the `hermes` wrapper (bin/hermes) when + # OpenRouter API key consumed by the `nomos` wrapper (bin/nomos) when # spawning a Goose session. Recipients are any host that should run a - # Nous-Hermes agent. Add a host's age_pubkey here, then + # Nomos agent. Add a host's age_pubkey here, then # `sops updatekeys -y secrets/openrouter-api-key.yaml`. - # See operations/hermes-agent.md. + # See operations/nomos-agent.md. age: >- age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6, age1vf8h7s8mqsn2q5eadgpdupsj4mwn8zguc77d85ws3xj40sl9rgksx2rxw6, diff --git a/AGENTS.md b/AGENTS.md index 221e2a9..622a052 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -124,9 +124,9 @@ per the DB-as-source-of-truth plan. ## 5. Acting on the homelab -- **Read state**: use MCP tools. Hermes (the AI agent) is the primary +- **Read state**: use MCP tools. Nomos (the AI agent) is the primary operator interface — it has 21 MCP tools for observe/orient/decide/act. -- **Actions** (restart, logs, apt, pct exec): Hermes calls `request_execution` +- **Actions** (restart, logs, apt, pct exec): Nomos calls `request_execution` via MCP. `reversible_low` actions execute immediately; `config_mutation` and `destructive` actions are queued for operator approval via Matrix. - **Secrets**: managed by Infisical (`oikos secret` subcommand for migration). @@ -152,10 +152,10 @@ Currently auto-setup: - **Caveman + templates** (`tools/setup-caveman.sh`): Installs Caveman npm package, wrapper scripts, and compact output templates for token-efficient CLI output. Wrapper at `~/bin/caveman_wrapper.sh`. -- **Hermes agent persona** (`tools/setup-hermes-soul.sh`): Provisions - `~/.hermes/SOUL.md` from `HERMES.md` on Hermes agents. This ensures every - Hermes agent follows the canonical homelab persona (token efficiency, source - of truth hierarchy). No-op on non-Hermes agents. +- **Nomos agent persona** (`tools/setup-nomos-soul.sh`): Provisions + `~/.nomos/SOUL.md` from `NOMOS.md` on Nomos agents. This ensures every + Nomos agent follows the canonical homelab persona (token efficiency, source + of truth hierarchy). No-op on non-Nomos agents. To add a new auto-setup, create `tools/.setup.sh` in the repo, commit and push. All enrolled clients pick it up within 5 minutes. diff --git a/CLIENTS.md b/CLIENTS.md index 9e5500b..69c483b 100644 --- a/CLIENTS.md +++ b/CLIENTS.md @@ -41,7 +41,7 @@ curl -fsSL https://git.hubris.network/dtoro/oikos/raw/main/bootstrap.sh | sudo b # Or with optional tooling: curl ... | sudo bash -s -- --with-mcp # wire Claude's MCP config -curl ... | sudo bash -s -- --with-hermes # install Goose + Hermes +curl ... | sudo bash -s -- --with-nomos # install Goose + Nomos ``` This calls `POST /api/v1/clients/enroll` on the Oikos API, which: @@ -56,7 +56,7 @@ This calls `POST /api/v1/clients/enroll` on the Oikos API, which: ### What changes on your machine - `/opt/homelab/` — agent orientation files (CLIENTS.md, AGENTS.md, OIKOS.md) -- `/opt/homelab/tools/` — tooling scripts (caveman, hermes-soul) +- `/opt/homelab/tools/` — tooling scripts (caveman, nomos-soul) - `/etc/age/key.txt` — age private key for SOPS decryption (fallback) - `/etc/infisical/identity` — Infisical machine identity (primary secrets) - Context poller — launchd/systemd timer hits `GET /api/v1/clients/{slug}/context` every 5 minutes for agent file updates diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 75b620e..7a10eaa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,7 @@ make build ``` cmd/oikos/ Single-binary entry point -cmd/hermes/ Hermes MCP client gateway +cmd/nomos/ Nomos MCP client gateway internal/ All Go packages httpapi/ REST + MCP server (OpenAPI-generated) mcp/ MCP tool implementations @@ -47,7 +47,7 @@ migrations/ Forward-only SQL migrations (TimescaleDB) seeds/ Bootstrap YAML: ontology, inventory, policy, knowledge compose/ Dockerfiles + Caddy config scripts/ Deploy, watchdog, rollback -hermes/ Hermes config, persona, skills +nomos/ Nomos config, persona, skills .agents/ Agent instruction files + skills plans/ Design documents docs/adr/ Architecture decision records diff --git a/README.md b/README.md index ac2d758..58db66f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Oikos Agentic homelab operating system written in Go. Single binary (`cmd/oikos`), -Docker-deployed on mac-mini, with a standalone Hermes MCP agent gateway -(`cmd/hermes`). Manages the **hubris** Proxmox homelab autonomously — observes +Docker-deployed on mac-mini, with a standalone Nomos MCP agent gateway +(`cmd/nomos`). Manages the **hubris** Proxmox homelab autonomously — observes state, classifies actions against policy, executes approved procedures over SSH, learns from outcomes, and escalates when uncertain. @@ -16,7 +16,7 @@ learns from outcomes, and escalates when uncertain. # Dev stack (postgres + api + scheduler + notifier) docker compose --profile dev up -d -# Full stack (adds Hermes agent gateway) +# Full stack (adds Nomos agent gateway) docker compose --profile full up -d # Build standalone binary @@ -33,7 +33,7 @@ OIKOS_DATABASE_URL="postgres://oikos:oikos_dev@localhost:5432/oikos?sslmode=disa ┌──────────────────────────────────┐ │ mac-mini (Docker) │ │ │ - Workstation ─── │ hermes (8092) ──MCP── api (8090) │ + Workstation ─── │ nomos (8092) ──MCP── api (8090) │ (mesh) │ MCP gateway REST + MCP │ │ │ │ scheduler ── notifier ── postgres │ @@ -46,7 +46,7 @@ OIKOS_DATABASE_URL="postgres://oikos:oikos_dev@localhost:5432/oikos?sslmode=disa | `oikos api` | 8090 | REST API + MCP server (15 tools) | | `oikos scheduler` | — | Probe runner, signal lifecycle, metrics | | `oikos notifier` | — | Approval tokens, Matrix alerts | -| `hermes serve` | 8092 | MCP client gateway, query routing | +| `nomos serve` | 8092 | MCP client gateway, query routing | ## Phases @@ -55,7 +55,7 @@ OIKOS_DATABASE_URL="postgres://oikos:oikos_dev@localhost:5432/oikos?sslmode=disa | 1 — Ontology + DB | ✅ | TimescaleDB, migrations, seeds, blast_radius | | 2 — API | ✅ | OpenAPI-first REST + MCP, auth, SSE, audit | | 3 — Control loop | ✅ | Scheduler, actuator, learning, classifier, notifier | -| 4 — Hermes agent | ✅ | Standalone MCP client gateway, agent activity | +| 4 — Nomos agent | ✅ | Standalone MCP client gateway, agent activity | | 5 — Secrets | ✅ | Infisical backend + SOPS fallback, rotation runbooks | | 6 — Deploy | ✅ | CI pipeline, cutover checklist, watchdog, rollback | @@ -71,7 +71,7 @@ curl http://localhost:8090/api/v1/health # fleet health curl http://localhost:8090/api/v1/agent-activity # agent log ``` -### Hermes queries +### Nomos queries ```bash # Structured tool call @@ -101,7 +101,7 @@ oikos secret migrate # SOPS → Infisical ``` cmd/oikos/ Go entry point — single binary -cmd/hermes/ Hermes MCP client gateway +cmd/nomos/ Nomos MCP client gateway internal/ Go packages (httpapi, mcp, scheduler, actuator, learning, notifier, policy, secrets, db, config, ontology, domain, knowledge) @@ -110,7 +110,7 @@ migrations/ Forward-only SQL migrations (TimescaleDB) seeds/ Bootstrap YAML (ontology, inventory, policy, knowledge) compose/ Dockerfiles + Caddy config scripts/ Deploy, watchdog, verification, rollback -hermes/ Hermes config, persona, skills +nomos/ Nomos config, persona, skills .agents/ Agent instruction files, shared conventions, skills archive/ Historical reference (legacy wiki, plans, SOPS backups) plans/ Design documents (active + done) diff --git a/bootstrap.sh b/bootstrap.sh index d7b6b1f..14cd9d2 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -3,7 +3,7 @@ # # Thin client model (rev 2): no git clone, no sync timer. Fetches only the # agent orientation files (CLIENTS.md, AGENTS.md, OIKOS.md) and tooling -# (caveman, hermes-soul) from the raw Gitea URL. Enrolls via the Oikos API +# (caveman, nomos-soul) from the raw Gitea URL. Enrolls via the Oikos API # to receive an age keypair and Infisical machine identity. A lightweight # context poller replaces the old 5-minute git pull. # @@ -11,7 +11,7 @@ # curl -fsSL https://git.hubris.network/dtoro/oikos/raw/main/bootstrap.sh \ # | sudo bash # curl ... | sudo bash -s -- --with-mcp # wire Claude's .mcp.json -# curl ... | sudo bash -s -- --with-hermes # install Goose + Hermes +# curl ... | sudo bash -s -- --with-nomos # install Goose + Nomos # curl ... | sudo bash -s -- --dry-run # show what would happen # # Prerequisites: @@ -28,11 +28,11 @@ REPO_RAW_URL="${HOMELAB_RAW_URL:-https://git.hubris.network/dtoro/oikos/raw/main OIKOS_API_URL="${HOMELAB_OIKOS_URL:-https://oikos.hubris.network/api/v1}" CLONE_DIR="${HOMELAB_CONTEXT_DIR:-/opt/homelab}" MCP_URL="${HOMELAB_MCP_URL:-https://mcp.hubris.network/mcp}" -HERMES_MCP_URI="${HOMELAB_HERMES_MCP_URI:-https://mcp.hubris.network/mcp}" -HERMES_MODEL="${HOMELAB_HERMES_MODEL:-nousresearch/hermes-4-405b}" +NOMOS_MCP_URI="${HOMELAB_NOMOS_MCP_URI:-https://mcp.hubris.network/mcp}" +NOMOS_MODEL="${HOMELAB_NOMOS_MODEL:-nousresearch/hermes-4-405b}" WITH_MCP=0 -WITH_HERMES=0 +WITH_NOMOS=0 DRY_RUN=0 GITEA_TOKEN="${HOMELAB_GITEA_TOKEN:-}" @@ -79,7 +79,7 @@ detect_mesh_ip() { while [ $# -gt 0 ]; do case "$1" in --with-mcp) WITH_MCP=1 ;; - --with-hermes) WITH_HERMES=1 ;; + --with-nomos) WITH_NOMOS=1 ;; --dry-run) DRY_RUN=1 ;; --gitea-token) GITEA_TOKEN="$2"; shift ;; --gitea-user) GITEA_USER="$2"; shift ;; @@ -148,7 +148,7 @@ done # ── fetch tools ────────────────────────────────────────────────────── log "fetching tools..." -for tool in setup-caveman.sh setup-hermes-soul.sh caveman.js caveman_wrapper.sh post-pull.sh; do +for tool in setup-caveman.sh setup-nomos-soul.sh caveman.js caveman_wrapper.sh post-pull.sh; do url="$REPO_RAW_URL/tools/${tool}" dest="$CLONE_DIR/tools/${tool}" dry mkdir -p "$(dirname "$dest")" @@ -319,15 +319,15 @@ if [ "$WITH_MCP" -eq 1 ]; then log " + MCP wired to $MCP_URL" fi -# ── --with-hermes: install Goose + Hermes wrapper ──────────────────── -if [ "$WITH_HERMES" -eq 1 ]; then - log "installing Hermes agent..." +# ── --with-nomos: install Goose + Nomos wrapper ──────────────────── +if [ "$WITH_NOMOS" -eq 1 ]; then + log "installing Nomos agent..." GOOSE_URL="https://github.com/block/goose/releases/latest/download/goose-${OS}-${ARCH:-amd64}" if [ "$OS" = Darwin ]; then GOOSE_URL="https://github.com/block/goose/releases/latest/download/goose-darwin-${ARCH:-arm64}"; fi dry curl -fsSL "$GOOSE_URL" -o /usr/local/bin/goose 2>/dev/null && chmod +x /usr/local/bin/goose || warn "goose not installed" - # Drop Hermes persona - cp "$CLONE_DIR/HERMES.md" "$CLONE_DIR/.agents/HERMES.md" 2>/dev/null || true - log " + Hermes agent installed" + # Drop Nomos persona + cp "$CLONE_DIR/NOMOS.md" "$CLONE_DIR/.agents/NOMOS.md" 2>/dev/null || true + log " + Nomos agent installed" fi # ── netbird SSH JWT cache ──────────────────────────────────────────── diff --git a/cmd/hermes/main.go b/cmd/nomos/main.go similarity index 92% rename from cmd/hermes/main.go rename to cmd/nomos/main.go index d78e8cf..28216bd 100644 --- a/cmd/hermes/main.go +++ b/cmd/nomos/main.go @@ -17,18 +17,18 @@ import ( func main() { if len(os.Args) < 2 { - fmt.Fprintln(os.Stderr, "usage: hermes serve") + fmt.Fprintln(os.Stderr, "usage: nomos serve") os.Exit(1) } - mcpURL := os.Getenv("HERMES_MCP_URL") + mcpURL := os.Getenv("NOMOS_MCP_URL") if mcpURL == "" { mcpURL = "http://localhost:8090/mcp" } - agentSlug := os.Getenv("HERMES_AGENT_SLUG") + agentSlug := os.Getenv("NOMOS_AGENT_SLUG") if agentSlug == "" { - agentSlug = "agent:hermes" + agentSlug = "agent:nomos" } switch os.Args[1] { @@ -38,7 +38,7 @@ func main() { client, err := newMCPClient(mcpURL) if err != nil { - slog.Error("hermes: mcp connect", "url", mcpURL, "error", err) + slog.Error("nomos: mcp connect", "url", mcpURL, "error", err) os.Exit(1) } @@ -51,21 +51,21 @@ func main() { handleQuery(w, r, client, agentSlug, mcpURL) }) - addr := os.Getenv("HERMES_LISTEN") + addr := os.Getenv("NOMOS_LISTEN") if addr == "" { addr = ":8092" } srv := &http.Server{Addr: addr, Handler: mux} go func() { - slog.Info("hermes: gateway listening", "addr", addr, "mcp", mcpURL) + slog.Info("nomos: gateway listening", "addr", addr, "mcp", mcpURL) if err := srv.ListenAndServe(); err != http.ErrServerClosed { - slog.Error("hermes: serve", "error", err) + slog.Error("nomos: serve", "error", err) } }() <-ctx.Done() - slog.Info("hermes: shutting down") + slog.Info("nomos: shutting down") srv.Shutdown(context.Background()) client.close() @@ -108,7 +108,7 @@ func handleQuery(w http.ResponseWriter, r *http.Request, client *mcpClient, agen duration := time.Since(start).Milliseconds() if err != nil { - slog.Error("hermes: query failed", "query", req.Query, "error", err) + slog.Error("nomos: query failed", "query", req.Query, "error", err) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]any{ "error": err.Error(), @@ -204,7 +204,7 @@ func newMCPClient(baseURL string) (*mcpClient, error) { resp, err := c.doRequest("initialize", map[string]any{ "protocolVersion": "2024-11-05", "capabilities": map[string]any{}, - "clientInfo": map[string]any{"name": "hermes", "version": "2.0"}, + "clientInfo": map[string]any{"name": "nomos", "version": "2.0"}, }) if err != nil { return nil, fmt.Errorf("initialize: %w", err) @@ -217,7 +217,7 @@ func newMCPClient(baseURL string) (*mcpClient, error) { // Send initialized notification c.doRequest("notifications/initialized", map[string]any{}) - slog.Info("hermes: mcp connected", "session", c.sessionID[:16]+"...") + slog.Info("nomos: mcp connected", "session", c.sessionID[:16]+"...") return c, nil } diff --git a/cmd/oikos/main.go b/cmd/oikos/main.go index b0e996d..98a4cf8 100644 --- a/cmd/oikos/main.go +++ b/cmd/oikos/main.go @@ -120,7 +120,7 @@ Roles: knowledge Convert wiki to knowledge seed (one-shot) version Print version info -The operator interface is Hermes (MCP agent) — no CLI needed. +The operator interface is Nomos (MCP agent) — no CLI needed. Environment: OIKOS_DATABASE_URL Postgres connection string OIKOS_API_LISTEN API listen address (default :8090) diff --git a/compose/caddy/Caddyfile.oikos b/compose/caddy/Caddyfile.oikos index 5947fc4..1ebf776 100644 --- a/compose/caddy/Caddyfile.oikos +++ b/compose/caddy/Caddyfile.oikos @@ -17,12 +17,12 @@ oikos.hubris.network { } } -# Oikos MCP endpoint (Hermes agents) — no auth required +# Oikos MCP endpoint (agents) — no auth required mcp.hubris.network { reverse_proxy :8090 } -# Hermes gateway (workstation access) -hermes.hubris.network { +# Nomos gateway (workstation access) — formerly hermes.hubris.network +nomos.hubris.network { reverse_proxy :8092 } diff --git a/compose/hermes/Dockerfile b/compose/hermes/Dockerfile deleted file mode 100644 index 84727f0..0000000 --- a/compose/hermes/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -# Hermes agent container — standalone MCP client gateway (Phase 4) -FROM golang:1.26-alpine AS builder - -RUN apk add --no-cache git ca-certificates - -WORKDIR /build -COPY go.mod go.sum ./ -RUN go mod download - -COPY . . - -RUN CGO_ENABLED=0 go build -o /hermes -tags timetzdata -ldflags="-s -w" ./cmd/hermes - -FROM gcr.io/distroless/static:nonroot - -COPY --from=builder /hermes /hermes -COPY hermes/ /app/hermes/ - -ENV HERMES_MCP_URL=http://api:8090/mcp -ENV HERMES_AGENT_SLUG=agent:hermes -ENV HERMES_LISTEN=:8092 - -EXPOSE 8092 - -ENTRYPOINT ["/hermes", "serve"] diff --git a/compose/nomos/Dockerfile b/compose/nomos/Dockerfile new file mode 100644 index 0000000..51a6377 --- /dev/null +++ b/compose/nomos/Dockerfile @@ -0,0 +1,25 @@ +# Nomos agent container — standalone MCP client gateway (Phase 4) +FROM golang:1.26-alpine AS builder + +RUN apk add --no-cache git ca-certificates + +WORKDIR /build +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +RUN CGO_ENABLED=0 go build -o /nomos -tags timetzdata -ldflags="-s -w" ./cmd/nomos + +FROM gcr.io/distroless/static:nonroot + +COPY --from=builder /nomos /nomos +COPY nomos/ /app/nomos/ + +ENV NOMOS_MCP_URL=http://api:8090/mcp +ENV NOMOS_AGENT_SLUG=agent:nomos +ENV NOMOS_LISTEN=:8092 + +EXPOSE 8092 + +ENTRYPOINT ["/nomos", "serve"] diff --git a/docker-compose.yml b/docker-compose.yml index ad4c60c..ea88174 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -60,7 +60,7 @@ services: OIKOS_API_LISTEN: ":8090" OIKOS_ENV: dev OIKOS_DEBUG: "true" - OIKOS_HERMES_AGENT_SLUG: ${OIKOS_HERMES_AGENT_SLUG:-agent:hermes} + OIKOS_NOMOS_AGENT_SLUG: ${OIKOS_NOMOS_AGENT_SLUG:-agent:nomos} volumes: - ${OIKOS_SSH_KEY_PATH:-~/.ssh/id_ed25519}:/etc/oikos/ssh_key:ro ports: @@ -107,18 +107,18 @@ services: stop_signal: SIGTERM stop_grace_period: 30s - # Hermes agent gateway (Phase 4) — mesh-published :8092 - hermes: + # Nomos agent gateway (Phase 4) — mesh-published :8092 + nomos: build: context: . - dockerfile: compose/hermes/Dockerfile + dockerfile: compose/nomos/Dockerfile profiles: ["full"] depends_on: api: condition: service_started environment: - HERMES_MCP_URL: http://api:8090/mcp - HERMES_AGENT_SLUG: agent:hermes + NOMOS_MCP_URL: http://api:8090/mcp + NOMOS_AGENT_SLUG: agent:nomos ports: - "8092:8092" stop_signal: SIGTERM diff --git a/docs/adr/0012-hermes-oikos-interactions.md b/docs/adr/0012-hermes-oikos-interactions.md index c954302..88e8788 100644 --- a/docs/adr/0012-hermes-oikos-interactions.md +++ b/docs/adr/0012-hermes-oikos-interactions.md @@ -230,4 +230,13 @@ sequenceDiagram `GET /clients/{slug}/context`. - **The DB is the single source of truth.** All state transitions, audit entries, and event emissions go through Postgres. The scheduler, - actuator, notifier, and API all read/write the same tables. \ No newline at end of file + actuator, notifier, and API all read/write the same tables. + +--- + +**2026-07-08 — renamed to Nomos.** The Hermes agent gateway was renamed to +Nomos (from *oikonomos*, the steward of the oikos) under the +[Nomos resident agent plan](../../plans/2026-07-08-nomos-resident-agent.md), +N0 milestone. The gateway binary (`cmd/nomos`), Docker service, DB slug +(`agent:nomos`), and all referencing docs were updated. All architectural +principles in this ADR remain unchanged. \ No newline at end of file diff --git a/docs/operations/rollback.md b/docs/operations/rollback.md index 3ac3e0e..1b27923 100644 --- a/docs/operations/rollback.md +++ b/docs/operations/rollback.md @@ -70,7 +70,7 @@ curl http://localhost:8090/healthz # Entity count matches curl -s http://localhost:8090/api/v1/entities?limit=1 | jq '.items | length' -# MCP tools working (via Hermes) +# MCP tools working (via Nomos) curl -s http://localhost:8092/query -d '{"tool":"get_health_summary"}' ``` diff --git a/internal/config/config.go b/internal/config/config.go index 1d3138a..368d5fc 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -20,7 +20,7 @@ type Config struct { // Auth (Phase 2: static bearer tokens + OIDC JWT) APIToken string // operator/CI bearer token for the REST API - MCPBearerToken string // shared secret for Hermes→API MCP calls + MCPBearerToken string // shared secret for Nomos→API MCP calls OIDCIssuer string // OIDC issuer URL for JWT validation (e.g. https://authentik.example.com/application/o/oikos/) OIDCClientID string // OIDC client ID (aud claim expected in JWT) @@ -54,9 +54,9 @@ type Config struct { // Approval HMAC secret (Phase 3) ApprovalHMACSecret string - // Hermes agent entity ID (Phase 4) - HermesAgentID string - HermesAgentSlug string + // Nomos agent entity ID (Phase 4) + NomosAgentID string + NomosAgentSlug string // Infisical (Phase 5) InfisicalSiteURL string @@ -151,11 +151,11 @@ func FromEnv() Config { if v := os.Getenv("OIKOS_APPROVAL_HMAC_SECRET"); v != "" { c.ApprovalHMACSecret = v } - if v := os.Getenv("OIKOS_HERMES_AGENT_ID"); v != "" { - c.HermesAgentID = v + if v := os.Getenv("OIKOS_NOMOS_AGENT_ID"); v != "" { + c.NomosAgentID = v } - if v := os.Getenv("OIKOS_HERMES_AGENT_SLUG"); v != "" { - c.HermesAgentSlug = v + if v := os.Getenv("OIKOS_NOMOS_AGENT_SLUG"); v != "" { + c.NomosAgentSlug = v } // Phase 5: Infisical secrets diff --git a/internal/httpapi/server.go b/internal/httpapi/server.go index f958098..2c34ce7 100644 --- a/internal/httpapi/server.go +++ b/internal/httpapi/server.go @@ -137,16 +137,16 @@ func NewHandler(ctx context.Context, pool *db.Pool, cfg config.Config) http.Hand r.With(combinedAuth(cfg)).Get("/api/v1/events/stream", s.serveSSE) // Mount MCP at /mcp (plan R3-10) - hermesAgentID := uuid.Nil - if cfg.HermesAgentID != "" { - if id, err := uuid.Parse(cfg.HermesAgentID); err == nil { - hermesAgentID = id + nomosAgentID := uuid.Nil + if cfg.NomosAgentID != "" { + if id, err := uuid.Parse(cfg.NomosAgentID); err == nil { + nomosAgentID = id } } - if hermesAgentID == uuid.Nil && cfg.HermesAgentSlug != "" { - _ = pool.QueryRow(ctx, "SELECT id FROM entities WHERE slug = $1", cfg.HermesAgentSlug).Scan(&hermesAgentID) + if nomosAgentID == uuid.Nil && cfg.NomosAgentSlug != "" { + _ = pool.QueryRow(ctx, "SELECT id FROM entities WHERE slug = $1", cfg.NomosAgentSlug).Scan(&nomosAgentID) } - r.With(combinedAuth(cfg)).Handle("/mcp", mcphandler.NewHandler(pool, cfg.MCPBearerToken, hermesAgentID)) + r.With(combinedAuth(cfg)).Handle("/mcp", mcphandler.NewHandler(pool, cfg.MCPBearerToken, nomosAgentID)) return r } diff --git a/internal/mcp/server.go b/internal/mcp/server.go index 8606946..7ab853c 100644 --- a/internal/mcp/server.go +++ b/internal/mcp/server.go @@ -37,7 +37,7 @@ func objSchema(props ...prop) *jsonschema.Schema { } // NewHandler creates an http.Handler that serves the Oikos MCP server. -// agentID is the Hermes agent entity UUID; tool calls are logged to agent_activity. +// agentID is the Nomos agent entity UUID; tool calls are logged to agent_activity. func NewHandler(pool *db.Pool, token string, agentID uuid.UUID) http.Handler { s := newServer(pool, agentID) handler := mcp.NewStreamableHTTPHandler(func(r *http.Request) *mcp.Server { @@ -258,7 +258,7 @@ func newServer(pool *db.Pool, agentID uuid.UUID) *mcp.Server { nStr(args["status"])), nil }) - register(&mcp.Tool{Name: "request_execution", Description: "Request a gated execution (Hermes-only mutation path). Supported actions: restart, systemctl, pct_exec, apt_upgrade.", + register(&mcp.Tool{Name: "request_execution", Description: "Request a gated execution (agent-only mutation path). Supported actions: restart, systemctl, pct_exec, apt_upgrade.", InputSchema: objSchema( prop{"target", "string", "Target entity slug (e.g. lxc:caddy)"}, prop{"action", "string", "Action: restart, systemctl, pct_exec, apt_upgrade"}, diff --git a/inventory.yaml b/inventory.yaml index 64743c0..1262480 100644 --- a/inventory.yaml +++ b/inventory.yaml @@ -511,7 +511,7 @@ hosts: 192.168.8.0/24. Re-enroll in mesh as a follow-up if off-LAN access to this host itself (not just its future guests) is needed. - First step of the planned library-SSD migration — see - .hermes/plans/2026-06-03_110000-library-ssd-migration-to-ludo-mini.md + .nomos/plans/2026-06-03_110000-library-ssd-migration-to-ludo-mini.md (filename kept as-is, it's a historical planning doc). Only Phase 1 (Proxmox install + cluster join) is done; no physical drive move, service migration, or GPU passthrough has happened yet. @@ -555,7 +555,7 @@ archaeology: kind: lxc pve_id: 123 destroyed: 2026-06-04 - reason: replaced by Hermes Agent on mac-mini; monitoring moved to homelab-health-watchdog cron + reason: replaced by Nomos Agent on mac-mini; monitoring moved to homelab-health-watchdog cron plato: kind: lxc pve_id: 126 diff --git a/migrations/014_rename_agent_hermes_to_nomos.up.sql b/migrations/014_rename_agent_hermes_to_nomos.up.sql new file mode 100644 index 0000000..65d653a --- /dev/null +++ b/migrations/014_rename_agent_hermes_to_nomos.up.sql @@ -0,0 +1,18 @@ +-- 014_rename_agent_hermes_to_nomos.up.sql +-- Rename the Hermes agent entity to Nomos (N0 milestone). +-- Identity-preserving: the UUID, relationships, and audit history survive. +-- The matching seed upsert will no-op because it upserts by slug. + +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM entities WHERE slug = 'agent:hermes') THEN + UPDATE entities + SET slug = 'agent:nomos', + name = 'nomos', + attributes = jsonb_set(attributes, '{name}', '"nomos"'), + updated_at = now() + WHERE slug = 'agent:hermes' + AND NOT EXISTS (SELECT 1 FROM entities WHERE slug = 'agent:nomos'); + END IF; +END +$$; diff --git a/hermes/SOUL.md b/nomos/SOUL.md similarity index 86% rename from hermes/SOUL.md rename to nomos/SOUL.md index 032b4bd..14db2e1 100644 --- a/hermes/SOUL.md +++ b/nomos/SOUL.md @@ -1,6 +1,6 @@ -# SOUL.md — Hermes agent persona (Phase 4, container runtime) +# SOUL.md — Nomos agent persona (Phase 4, container runtime) -You are **Hermes**, the homelab AI agent running in a Docker container on +You are **Nomos** (from *oikonomos*, the steward of the oikos), the homelab AI agent running in a Docker container on mac-mini. You operate in **gateway mode** on mesh-only port 8092. ## Source of truth @@ -45,6 +45,6 @@ describing state, be concise — the operator reads your output in Matrix. ## Skills -Skills live in `/app/hermes/skills/`. Load a skill when its description +Skills live in `/app/nomos/skills/`. Load a skill when its description matches the task. The `homelab-ops` skill covers: - Health checks, signal triage, pattern validation, and escalation flow. diff --git a/hermes/config.yaml b/nomos/config.yaml similarity index 74% rename from hermes/config.yaml rename to nomos/config.yaml index 2afe1af..321498f 100644 --- a/hermes/config.yaml +++ b/nomos/config.yaml @@ -1,16 +1,16 @@ -# Hermes agent config — standalone MCP client gateway (Phase 4) +# Nomos agent config — standalone MCP client gateway (Phase 4) mcp: - endpoint: ${HERMES_MCP_URL}?session_id=${HERMES_SESSION_ID} + endpoint: ${NOMOS_MCP_URL}?session_id=${NOMOS_SESSION_ID} transport: streamable_http server: - listen: ${HERMES_LISTEN} + listen: ${NOMOS_LISTEN} mesh_only: true agent: - name: hermes - slug: ${HERMES_AGENT_SLUG} + name: nomos + slug: ${NOMOS_AGENT_SLUG} query_routing: # Maps natural-language query patterns to MCP tools diff --git a/hermes/skills/homelab-ops/SKILL.md b/nomos/skills/homelab-ops/SKILL.md similarity index 85% rename from hermes/skills/homelab-ops/SKILL.md rename to nomos/skills/homelab-ops/SKILL.md index db64433..716ad31 100644 --- a/hermes/skills/homelab-ops/SKILL.md +++ b/nomos/skills/homelab-ops/SKILL.md @@ -6,7 +6,7 @@ ## Overview -Standard operating procedures for the Hermes agent managing the hubris +Standard operating procedures for the Nomos agent managing the hubris homelab. All mutations route through `request_execution` → Oikos policy gating → actuator (SSH). @@ -41,5 +41,8 @@ gating → actuator (SSH). ## Changelog +### 2026-07-08 — rename to Nomos +Agent renamed from Hermes to Nomos (N0 milestone). + ### 2026-07-07 — initial Phase 4 skill -Baseline homelab operations skill for Hermes container. +Baseline homelab operations skill for Nomos container. diff --git a/plans/2026-07-08-nomos-resident-agent.md b/plans/2026-07-08-nomos-resident-agent.md index 080c938..84b3c2e 100644 --- a/plans/2026-07-08-nomos-resident-agent.md +++ b/plans/2026-07-08-nomos-resident-agent.md @@ -1,6 +1,6 @@ # 2026-07-08 — Nomos resident agent (renames Hermes) -**Status:** Planned +**Status:** In Progress — N0 complete 2026-07-08 ## Goal diff --git a/plans/index.md b/plans/index.md index f06dff4..f0076bf 100644 --- a/plans/index.md +++ b/plans/index.md @@ -12,7 +12,7 @@ went sideways, open an investigation. | 2026-07-08 | [Plan vs implementation cross-reference](2026-07-08-plan-implementation-audit.md) | Planned | | 2026-07-08 | [Oikos gaps, broken things, and improvements](2026-07-08-oikos-gaps-and-improvements.md) | Planned | | 2026-07-08 | [Control room web UI](2026-07-08-control-room-webui.md) | Planned | -| 2026-07-08 | [Nomos resident agent (renames Hermes)](2026-07-08-nomos-resident-agent.md) | Planned | +| 2026-07-08 | [Nomos resident agent (renames Hermes)](2026-07-08-nomos-resident-agent.md) | In Progress | ## Done diff --git a/scripts/cutover-checklist.md b/scripts/cutover-checklist.md index 5752b6c..2fb750f 100644 --- a/scripts/cutover-checklist.md +++ b/scripts/cutover-checklist.md @@ -6,7 +6,7 @@ Status: [x] = done, [ ] = pending - [x] **Backup**: `pg_dump oikos > backups/pre-cutover-20260707.sql` (145K) - [x] **CI green**: pushed to main, `.gitea/workflows/ci.yml` exists -- [x] **Deploy test**: Docker stack running with api + scheduler + notifier + hermes +- [x] **Deploy test**: Docker stack running with api + scheduler + notifier + nomos - [x] **Caddy config**: `compose/caddy/Caddyfile.oikos` pushed to `dtoro/caddy-conf` (ed20908). Auto-deploys to caddy (121). - [x] **DNS**: `oikos.hubris.network` already resolves to 192.168.8.175 (mac-mini mesh) - [x] **Secrets**: Infisical bootstrapped + migration complete 2026-07-07. All 11 SOPS secrets migrated to Infisical (oikos project, dev env). Machine identity `oikos-api` has RW access verified via Go SDK. ENCRYPTION_KEY must be 32-char raw string (docs incorrect). SOPS fallback preserved for DR. secrets-issuance decommissioned — stopped/disabled on apps/105; superseded by Infisical. @@ -23,7 +23,7 @@ Status: [x] = done, [ ] = pending ## Post-cutover verification - [x] **./scripts/verify-phase6.sh** — all 14 checks pass -- [x] **Hermes query**: `curl http://localhost:8092/query -d '{"query":"fleet health"}'` → HTTP 200 +- [x] **Nomos query**: `curl http://localhost:8092/query -d '{"query":"fleet health"}'` → HTTP 200 - [x] **Agent activity**: `curl http://localhost:8090/api/v1/agent-activity` → returns data - [x] **Scheduler ticking**: 30s ticks logged - [x] **Notifier polling**: running diff --git a/scripts/verify-phase6.sh b/scripts/verify-phase6.sh index 598d44c..45fabcc 100755 --- a/scripts/verify-phase6.sh +++ b/scripts/verify-phase6.sh @@ -27,7 +27,7 @@ check "4. Scheduler: check pass" "http://localhost:8090/api/v1/check check "5. Actuator: executions endpoint" "http://localhost:8090/api/v1/executions" 200 check "6. Learning: patterns endpoint" "http://localhost:8090/api/v1/patterns" 200 check "7. Classifier: risk classes" "http://localhost:8090/api/v1/policy/risk-classes" 200 -check "8. Hermes: gateway health" "http://localhost:8092/healthz" 200 +check "8. Nomos: gateway health" "http://localhost:8092/healthz" 200 check "9. Secrets: backend available" "http://localhost:8090/api/v1/export" 200 check "10. Deploy: events endpoint" "http://localhost:8090/api/v1/events" 200 check "11. Knowledge: content search" "http://localhost:8090/healthz" 200 diff --git a/seeds/inventory.yaml b/seeds/inventory.yaml index ee68c81..cfc88e4 100644 --- a/seeds/inventory.yaml +++ b/seeds/inventory.yaml @@ -325,7 +325,7 @@ entities: attributes: {matrix_id: "@dtoro:avispero"}} - {slug: "idp:authentik", type: identity-provider, name: authentik, attributes: {issuer: "https://auth.hubris.network", auth_mode: both}} - - {slug: "agent:hermes", type: agent, name: hermes, + - {slug: "agent:nomos", type: agent, name: nomos, state: active, attributes: {gateway_port: 8092, session_mode: smart_approve, note: "Phase 4 — Docker gateway mode"}} - {slug: "agent:oikos", type: agent, name: oikos, @@ -334,7 +334,7 @@ entities: # ─── Archaeology (state: destroyed — kept for "what happened to X?") ─ - {slug: "lxc:claudio-bot", type: lxc, name: claudio-bot, state: destroyed, - attributes: {pve_id: 123, destroyed: "2026-06-04", reason: "replaced by Hermes Agent on mac-mini"}} + attributes: {pve_id: 123, destroyed: "2026-06-04", reason: "replaced by Nomos Agent on mac-mini"}} - {slug: "lxc:plato", type: lxc, name: plato, state: destroyed, attributes: {pve_id: 126, destroyed: "2026-06-28", reason: "notes workspace decommissioned; data at /mnt/library/documents/plato"}} - {slug: "lxc:mule-photos-new", type: lxc, name: mule-photos-new, state: destroyed, @@ -538,5 +538,5 @@ relationships: - {source: "lxc:rclone", target: "backup:proton-drive", type: backs-up-to} # ─── Governance ──────────────────────────────────────────────────── - - {source: "person:dtoro", target: "agent:hermes", type: owns} + - {source: "person:dtoro", target: "agent:nomos", type: owns} - {source: "person:dtoro", target: "agent:oikos", type: owns} diff --git a/seeds/ontology.yaml b/seeds/ontology.yaml index a5e86fe..9e3b64e 100644 --- a/seeds/ontology.yaml +++ b/seeds/ontology.yaml @@ -531,7 +531,7 @@ entity_types: domain: identity layer: governance lifecycle: infrastructure # agents are deployed/retired like infrastructure - description: Software agent actor (Hermes, the Oikos control loop). + description: Software agent actor (Nomos, the Oikos control loop). attributes: type: object properties: diff --git a/tools/setup-hermes-soul.sh b/tools/setup-hermes-soul.sh deleted file mode 100755 index 4b96955..0000000 --- a/tools/setup-hermes-soul.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -# setup-hermes-soul.sh — provision Hermes agent persona. -# Copies ~/.hermes/SOUL.md from hermes/SOUL.md. No-op on non-Hermes agents. -set -euo pipefail - -CLONE_DIR="${HOMELAB_CONTEXT_DIR:-/opt/homelab}" - -if [ -f "$CLONE_DIR/hermes/SOUL.md" ]; then - mkdir -p "$HOME/.hermes" - cp "$CLONE_DIR/hermes/SOUL.md" "$HOME/.hermes/SOUL.md" - echo "[setup-hermes-soul] SOUL.md provisioned" -else - echo "[setup-hermes-soul] no hermes/SOUL.md found; skipping" -fi \ No newline at end of file diff --git a/tools/setup-nomos-soul.sh b/tools/setup-nomos-soul.sh new file mode 100644 index 0000000..aa50cde --- /dev/null +++ b/tools/setup-nomos-soul.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# setup-nomos-soul.sh — provision Nomos agent persona. +# Copies ~/.nomos/SOUL.md from nomos/SOUL.md. No-op on non-Nomos agents. +set -euo pipefail + +CLONE_DIR="${HOMELAB_CONTEXT_DIR:-/opt/homelab}" + +if [ -f "$CLONE_DIR/nomos/SOUL.md" ]; then + mkdir -p "$HOME/.nomos" + cp "$CLONE_DIR/nomos/SOUL.md" "$HOME/.nomos/SOUL.md" + echo "[setup-nomos-soul] SOUL.md provisioned" +else + echo "[setup-nomos-soul] no nomos/SOUL.md found; skipping" +fi