N0: rename Hermes → Nomos (standalone commit)

Problem: "Hermes" collides with Nous Researchs unrelated product;
  unclear identity for the resident agent.

  Change: Rename the live service identity across 39 files:
  - cmd/hermes/ → cmd/nomos/ (binary, env vars NOMOS_*)
  - internal/config/ server.go (NomosAgentSlug, nomosAgentID)
  - compose/hermes/ → compose/nomos/ (Dockerfile, service name)
  - hermes/ → nomos/ (SOUL.md, config.yaml, skills/)
  - .agents/HERMES.md → NOMOS.md (persona)
  - tools/setup-hermes-soul.sh → setup-nomos-soul.sh
  - seeds/inventory.yaml (agent:hermes → agent:nomos)
  - migrations/014_rename_agent_hermes_to_nomos.up.sql
  - Caddy vhost hermes.hubris.network → nomos.hubris.network
  - All referencing docs, scripts, ADR notes

  History preserved: archive/, plans/done/, ADRs not rewritten.
  Matrix @hermes notifier account and Legacy bin/hermes on LXC 129
  intentionally untouched (out of scope).

  Risk: N0 is identity-only rename; zero behavioral changes.
  Verification: go build ./... passes; docker compose --profile full
  resolves nomos service; grep -ri hermes (excluding archive/plans)
  returns only intentional refs (LLM model name, Matrix user).
This commit is contained in:
2026-07-08 14:14:56 +02:00
parent 9d9cbb63c4
commit 2b3aa248b1
39 changed files with 230 additions and 200 deletions

View File

@@ -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
bash /opt/homelab-context/tools/setup-nomos-soul.sh

View File

@@ -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`).

View File

@@ -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.

View File

@@ -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.

View File

@@ -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/<id>/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 <service>` | Dry-run report before mutating: risk class, current health, config repo, verification command |
| `homelab decide <action> <entity>` | 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 <service> [--approval-id <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/).

View File

@@ -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 <host> --finalize-pubkey <age1...> --with-hermes` |
| The host's `age_pubkey` is on the openrouter-api-key.yaml sops rule | `homelab client add <host> --finalize-pubkey <age1...> --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 <host> | grep age_pubkey | awk '{print $2}')
homelab client add <host> --finalize-pubkey "$PUBKEY" --with-hermes
homelab client add <host> --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 <host> --finalize-pubkey <age1...> --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 <host> --finalize-pubkey <age1...> --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.

View File

@@ -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/<name>/SKILL.md` for Claude Code.
Copy to `~/.nomos/skills/` for Nomos agent, or `~/.claude/projects/<name>/SKILL.md` for Claude Code.

View File

@@ -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 <hostname>

4
.gitignore vendored
View File

@@ -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).

View File

@@ -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,

View File

@@ -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/<name>.setup.sh` in the repo,
commit and push. All enrolled clients pick it up within 5 minutes.

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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 ────────────────────────────────────────────

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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 <mac-mini-mesh-ip>:8090
}
# Hermes gateway (workstation access)
hermes.hubris.network {
# Nomos gateway (workstation access) — formerly hermes.hubris.network
nomos.hubris.network {
reverse_proxy <mac-mini-mesh-ip>:8092
}

View File

@@ -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"]

25
compose/nomos/Dockerfile Normal file
View File

@@ -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"]

View File

@@ -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

View File

@@ -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.
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.

View File

@@ -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"}'
```

View File

@@ -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

View File

@@ -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
}

View File

@@ -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"},

View File

@@ -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

View File

@@ -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
$$;

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -1,6 +1,6 @@
# 2026-07-08 — Nomos resident agent (renames Hermes)
**Status:** Planned
**Status:** In Progress — N0 complete 2026-07-08
## Goal

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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}

View File

@@ -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:

View File

@@ -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

14
tools/setup-nomos-soul.sh Normal file
View File

@@ -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