From bad6fc67336bb1323b1d2120c12169984056d9fd Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Thu, 9 Apr 2026 03:16:41 +0000 Subject: [PATCH] feat: hermes frame mog openclaw --- config/{openclaw => hermes}/HEARTBEAT.md | 0 config/{openclaw => hermes}/SOUL.md | 0 config/{openclaw => hermes}/TOOLS.md | 0 config/openclaw/env.example | 3 - flake.lock | 285 ++++++++++++++--------- flake.nix | 4 +- home/common.nix | 2 +- home/hermes.nix | 12 + home/openclaw.nix | 37 --- hosts/netty/configuration.nix | 2 +- hosts/netty/forgejo.nix | 5 + hosts/netty/hermes-gateway.nix | 77 ++++++ hosts/netty/nginx.nix | 4 +- hosts/netty/openclaw-gateway.nix | 68 ------ modules/hosts/netty.nix | 2 +- 15 files changed, 279 insertions(+), 222 deletions(-) rename config/{openclaw => hermes}/HEARTBEAT.md (100%) rename config/{openclaw => hermes}/SOUL.md (100%) rename config/{openclaw => hermes}/TOOLS.md (100%) delete mode 100644 config/openclaw/env.example create mode 100644 home/hermes.nix delete mode 100644 home/openclaw.nix create mode 100644 hosts/netty/hermes-gateway.nix delete mode 100644 hosts/netty/openclaw-gateway.nix diff --git a/config/openclaw/HEARTBEAT.md b/config/hermes/HEARTBEAT.md similarity index 100% rename from config/openclaw/HEARTBEAT.md rename to config/hermes/HEARTBEAT.md diff --git a/config/openclaw/SOUL.md b/config/hermes/SOUL.md similarity index 100% rename from config/openclaw/SOUL.md rename to config/hermes/SOUL.md diff --git a/config/openclaw/TOOLS.md b/config/hermes/TOOLS.md similarity index 100% rename from config/openclaw/TOOLS.md rename to config/hermes/TOOLS.md diff --git a/config/openclaw/env.example b/config/openclaw/env.example deleted file mode 100644 index 605e80c..0000000 --- a/config/openclaw/env.example +++ /dev/null @@ -1,3 +0,0 @@ -OPENCLAW_GATEWAY_TOKEN=replace-me-with-a-long-random-token -TELEGRAM_BOT_TOKEN=123456:replace-me -ANTHROPIC_API_KEY=sk-ant-replace-me diff --git a/flake.lock b/flake.lock index 43c32fc..5452ded 100644 --- a/flake.lock +++ b/flake.lock @@ -77,6 +77,27 @@ } }, "flake-parts_2": { + "inputs": { + "nixpkgs-lib": [ + "hermes-agent", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1772408722, + "narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_3": { "inputs": { "nixpkgs-lib": [ "neovim-nightly", @@ -133,24 +154,6 @@ "type": "github" } }, - "flake-utils_3": { - "inputs": { - "systems": "systems_3" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "googleworkspace-cli": { "inputs": { "flake-utils": "flake-utils_2", @@ -172,6 +175,30 @@ "type": "github" } }, + "hermes-agent": { + "inputs": { + "flake-parts": "flake-parts_2", + "nixpkgs": [ + "nixpkgs" + ], + "pyproject-build-systems": "pyproject-build-systems", + "pyproject-nix": "pyproject-nix_2", + "uv2nix": "uv2nix_2" + }, + "locked": { + "lastModified": 1775687993, + "narHash": "sha256-RlAoMbnW23RCxBsPb5S8/ntf92X0pTodADnYDWGzr5Y=", + "owner": "NousResearch", + "repo": "hermes-agent", + "rev": "8de91ce9d22fd979f6ab61a4e5ff9da5e1c69647", + "type": "github" + }, + "original": { + "owner": "NousResearch", + "repo": "hermes-agent", + "type": "github" + } + }, "home-manager": { "inputs": { "nixpkgs": [ @@ -192,30 +219,9 @@ "type": "github" } }, - "home-manager_2": { - "inputs": { - "nixpkgs": [ - "openClaw", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1767909183, - "narHash": "sha256-u/bcU0xePi5bgNoRsiqSIwaGBwDilKKFTz3g0hqOBAo=", - "owner": "nix-community", - "repo": "home-manager", - "rev": "cd6e96d56ed4b2a779ac73a1227e0bb1519b3509", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "home-manager", - "type": "github" - } - }, "neovim-nightly": { "inputs": { - "flake-parts": "flake-parts_2", + "flake-parts": "flake-parts_3", "neovim-src": "neovim-src", "nixpkgs": [ "nixpkgs" @@ -290,24 +296,6 @@ "type": "github" } }, - "nix-steipete-tools": { - "inputs": { - "nixpkgs": "nixpkgs_2" - }, - "locked": { - "lastModified": 1773561580, - "narHash": "sha256-wT0bKTp45YnMkc4yXQvk943Zz/rksYiIjEXGdWzxnic=", - "owner": "openclaw", - "repo": "nix-steipete-tools", - "rev": "cd4c429ff3b3aaef9f92e59812cf2baf5704b86f", - "type": "github" - }, - "original": { - "owner": "openclaw", - "repo": "nix-steipete-tools", - "type": "github" - } - }, "nixpkgs": { "locked": { "lastModified": 1774701658, @@ -340,22 +328,6 @@ } }, "nixpkgs_2": { - "locked": { - "lastModified": 1767364772, - "narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_3": { "locked": { "lastModified": 1767640445, "narHash": "sha256-UWYqmD7JFBEDBHWYcqE6s6c77pWdcU/i+bwD6XxMb8A=", @@ -371,32 +343,9 @@ "type": "github" } }, - "openClaw": { - "inputs": { - "flake-utils": "flake-utils_3", - "home-manager": "home-manager_2", - "nix-steipete-tools": "nix-steipete-tools", - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1773851886, - "narHash": "sha256-+3ygZuf5K8mtSGMMEZ/h+vxGvXCu1CmiB+531KMagH8=", - "owner": "openclaw", - "repo": "nix-openclaw", - "rev": "64d410666821866c565e048a4d07d6cf5d8e494e", - "type": "github" - }, - "original": { - "owner": "openclaw", - "repo": "nix-openclaw", - "type": "github" - } - }, "openspec": { "inputs": { - "nixpkgs": "nixpkgs_3" + "nixpkgs": "nixpkgs_2" }, "locked": { "lastModified": 1772182342, @@ -412,18 +361,106 @@ "type": "github" } }, + "pyproject-build-systems": { + "inputs": { + "nixpkgs": [ + "hermes-agent", + "nixpkgs" + ], + "pyproject-nix": "pyproject-nix", + "uv2nix": "uv2nix" + }, + "locked": { + "lastModified": 1772555609, + "narHash": "sha256-3BA3HnUvJSbHJAlJj6XSy0Jmu7RyP2gyB/0fL7XuEDo=", + "owner": "pyproject-nix", + "repo": "build-system-pkgs", + "rev": "c37f66a953535c394244888598947679af231863", + "type": "github" + }, + "original": { + "owner": "pyproject-nix", + "repo": "build-system-pkgs", + "type": "github" + } + }, + "pyproject-nix": { + "inputs": { + "nixpkgs": [ + "hermes-agent", + "pyproject-build-systems", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1769936401, + "narHash": "sha256-kwCOegKLZJM9v/e/7cqwg1p/YjjTAukKPqmxKnAZRgA=", + "owner": "nix-community", + "repo": "pyproject.nix", + "rev": "b0d513eeeebed6d45b4f2e874f9afba2021f7812", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "pyproject.nix", + "type": "github" + } + }, + "pyproject-nix_2": { + "inputs": { + "nixpkgs": [ + "hermes-agent", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1772865871, + "narHash": "sha256-/ZTSg97aouL0SlPHaokA4r3iuH9QzHVuWPACD2CUCFY=", + "owner": "pyproject-nix", + "repo": "pyproject.nix", + "rev": "e537db02e72d553cea470976b9733581bcf5b3ed", + "type": "github" + }, + "original": { + "owner": "pyproject-nix", + "repo": "pyproject.nix", + "type": "github" + } + }, + "pyproject-nix_3": { + "inputs": { + "nixpkgs": [ + "hermes-agent", + "uv2nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1771518446, + "narHash": "sha256-nFJSfD89vWTu92KyuJWDoTQJuoDuddkJV3TlOl1cOic=", + "owner": "pyproject-nix", + "repo": "pyproject.nix", + "rev": "eb204c6b3335698dec6c7fc1da0ebc3c6df05937", + "type": "github" + }, + "original": { + "owner": "pyproject-nix", + "repo": "pyproject.nix", + "type": "github" + } + }, "root": { "inputs": { "claudeCode": "claudeCode", "disko": "disko", "flake-parts": "flake-parts", "googleworkspace-cli": "googleworkspace-cli", + "hermes-agent": "hermes-agent", "home-manager": "home-manager", "neovim-nightly": "neovim-nightly", "nix-darwin": "nix-darwin", "nix-homebrew": "nix-homebrew", "nixpkgs": "nixpkgs", - "openClaw": "openClaw", "openspec": "openspec" } }, @@ -457,18 +494,52 @@ "type": "github" } }, - "systems_3": { + "uv2nix": { + "inputs": { + "nixpkgs": [ + "hermes-agent", + "pyproject-build-systems", + "nixpkgs" + ], + "pyproject-nix": [ + "hermes-agent", + "pyproject-build-systems", + "pyproject-nix" + ] + }, "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "lastModified": 1770770348, + "narHash": "sha256-A2GzkmzdYvdgmMEu5yxW+xhossP+txrYb7RuzRaqhlg=", + "owner": "pyproject-nix", + "repo": "uv2nix", + "rev": "5d1b2cb4fe3158043fbafbbe2e46238abbc954b0", "type": "github" }, "original": { - "owner": "nix-systems", - "repo": "default", + "owner": "pyproject-nix", + "repo": "uv2nix", + "type": "github" + } + }, + "uv2nix_2": { + "inputs": { + "nixpkgs": [ + "hermes-agent", + "nixpkgs" + ], + "pyproject-nix": "pyproject-nix_3" + }, + "locked": { + "lastModified": 1773039484, + "narHash": "sha256-+boo33KYkJDw9KItpeEXXv8+65f7hHv/earxpcyzQ0I=", + "owner": "pyproject-nix", + "repo": "uv2nix", + "rev": "b68be7cfeacbed9a3fa38a2b5adc0cfb81d9bb1f", + "type": "github" + }, + "original": { + "owner": "pyproject-nix", + "repo": "uv2nix", "type": "github" } } diff --git a/flake.nix b/flake.nix index 56253cf..f47a345 100644 --- a/flake.nix +++ b/flake.nix @@ -25,8 +25,8 @@ inputs.nixpkgs.follows = "nixpkgs"; }; - openClaw = { - url = "github:openclaw/nix-openclaw"; + hermes-agent = { + url = "github:NousResearch/hermes-agent"; inputs.nixpkgs.follows = "nixpkgs"; }; diff --git a/home/common.nix b/home/common.nix index cf8b8c5..4cba3ae 100644 --- a/home/common.nix +++ b/home/common.nix @@ -21,7 +21,7 @@ ./mise.nix ./migration.nix ./nvim.nix - ./openclaw.nix + ./hermes.nix ./prompt.nix ./skills.nix ./scripts.nix diff --git a/home/hermes.nix b/home/hermes.nix new file mode 100644 index 0000000..c5c6abc --- /dev/null +++ b/home/hermes.nix @@ -0,0 +1,12 @@ +{ + inputs, + pkgs, + hostConfig, + lib, + ... +}: +lib.mkIf hostConfig.isLinux { + home.packages = [ + inputs.hermes-agent.packages.${pkgs.stdenv.hostPlatform.system}.default + ]; +} diff --git a/home/openclaw.nix b/home/openclaw.nix deleted file mode 100644 index b814e50..0000000 --- a/home/openclaw.nix +++ /dev/null @@ -1,37 +0,0 @@ -{ - config, - inputs, - pkgs, - hostConfig, - lib, - ... -}: -let - openClawVersion = "2026.4.5"; - npmDir = "${config.xdg.dataHome}/npm"; -in -lib.mkIf hostConfig.isLinux { - home.packages = [ - inputs.openClaw.packages.${pkgs.stdenv.hostPlatform.system}.default - ]; - - home.activation.installOpenClaw = lib.hm.dag.entryAfter [ "writeBoundary" ] '' - export PATH="${lib.makeBinPath [ pkgs.nodejs_22 pkgs.coreutils ]}:$PATH" - export NPM_CONFIG_USERCONFIG="${config.xdg.configHome}/npm/npmrc" - export XDG_DATA_HOME="${config.xdg.dataHome}" - export XDG_CACHE_HOME="${config.xdg.cacheHome}" - - OPENCLAW_DIR="${npmDir}/lib/node_modules/openclaw" - INSTALLED=$(npm ls -g openclaw --depth=0 --json 2>/dev/null | ${pkgs.jq}/bin/jq -r '.dependencies.openclaw.version // empty') - HEALTHY=true - [ "$INSTALLED" != "${openClawVersion}" ] && HEALTHY=false - [ ! -d "$OPENCLAW_DIR/node_modules/grammy" ] && HEALTHY=false - if [ "$HEALTHY" = false ]; then - npm install -g "openclaw@${openClawVersion}" --force 2>/dev/null || true - fi - ''; - - home.file.".openclaw/workspace/SOUL.md".source = ../config/openclaw/SOUL.md; - home.file.".openclaw/workspace/TOOLS.md".source = ../config/openclaw/TOOLS.md; - home.file.".openclaw/workspace/HEARTBEAT.md".source = ../config/openclaw/HEARTBEAT.md; -} diff --git a/hosts/netty/configuration.nix b/hosts/netty/configuration.nix index dd18a40..b0c12b5 100644 --- a/hosts/netty/configuration.nix +++ b/hosts/netty/configuration.nix @@ -18,7 +18,7 @@ in ./vaultwarden.nix ./forgejo.nix ./betternas.nix - ./openclaw-gateway.nix + ./hermes-gateway.nix ./forgejo-runner.nix ../../modules/base.nix (modulesPath + "/profiles/minimal.nix") diff --git a/hosts/netty/forgejo.nix b/hosts/netty/forgejo.nix index 33c73d2..b162fb4 100644 --- a/hosts/netty/forgejo.nix +++ b/hosts/netty/forgejo.nix @@ -137,9 +137,11 @@ in clean_url=$(printf '%s' "$clone_url" | sed 's|https://oauth2:[^@]*@github.com/|https://github.com/|') local repo_id repo_id=$(sqlite3 /var/lib/forgejo/data/forgejo.db \ + ".timeout 5000" \ "SELECT r.id FROM repository r JOIN \"user\" u ON r.owner_id=u.id WHERE u.lower_name=LOWER('$forgejo_owner') AND r.lower_name=LOWER('$repo_name');") if [ -n "$repo_id" ]; then sqlite3 /var/lib/forgejo/data/forgejo.db \ + ".timeout 5000" \ "UPDATE mirror SET remote_address='$clean_url' WHERE repo_id=$repo_id AND remote_address LIKE '%ghp_%';" fi } @@ -332,6 +334,7 @@ in # find the latest commit we already recorded for this repo latest=$(sqlite3 "$DB" \ + ".timeout 5000" \ "SELECT COALESCE(MAX(created_unix),0) FROM action WHERE repo_id=$repo_id AND act_user_id=$user_id AND op_type=$OP_TYPE;") # convert to ISO 8601 "since" param (skip if no prior records -> fetch all) @@ -372,10 +375,12 @@ in # deduplicate on repo + user + timestamp exists=$(sqlite3 "$DB" \ + ".timeout 5000" \ "SELECT COUNT(*) FROM action WHERE user_id=$user_id AND repo_id=$repo_id AND op_type=$OP_TYPE AND created_unix=$created_unix;") [ "$exists" -gt 0 ] && continue sqlite3 "$DB" \ + ".timeout 5000" \ "INSERT INTO action (user_id, op_type, act_user_id, repo_id, ref_name, is_private, content, created_unix) VALUES ($user_id, $OP_TYPE, $user_id, $repo_id, 'refs/heads/$branch', 1, '$content', $created_unix);" repo_added=$((repo_added + 1)) diff --git a/hosts/netty/hermes-gateway.nix b/hosts/netty/hermes-gateway.nix new file mode 100644 index 0000000..caa4560 --- /dev/null +++ b/hosts/netty/hermes-gateway.nix @@ -0,0 +1,77 @@ +{ + inputs, + pkgs, + username, + ... +}: +let + homeDir = "/home/${username}"; + stateDir = "${homeDir}/.hermes"; +in +{ + # The hermes-agent NixOS module orders its activation script after + # "setupSecrets" (sops-nix / agenix). We don't use either, so + # provide a no-op to satisfy the dependency. + system.activationScripts.setupSecrets = ""; + + services.hermes-agent = { + enable = true; + package = inputs.hermes-agent.packages.${pkgs.stdenv.hostPlatform.system}.default; + user = username; + group = "users"; + createUser = false; + stateDir = stateDir; + workingDirectory = "${stateDir}/workspace"; + addToSystemPackages = false; + + environmentFiles = [ "${stateDir}/.env" ]; + environment = { + HERMES_MANAGED = "true"; + }; + + documents = { + "SOUL.md" = ../../config/hermes/SOUL.md; + "TOOLS.md" = ../../config/hermes/TOOLS.md; + "HEARTBEAT.md" = ../../config/hermes/HEARTBEAT.md; + }; + + settings = { + model = { + provider = "openai-codex"; + model = "gpt-5.4"; + }; + agent = { + max_turns = 100; + verbose = false; + }; + terminal = { + backend = "local"; + }; + compression = { + enabled = true; + }; + memory = { + memory_enabled = true; + user_profile_enabled = true; + }; + toolsets = [ "coding" ]; + channels = { + telegram = { + bot_token = "\${TELEGRAM_BOT_TOKEN}"; + dm_policy = "pairing"; + }; + }; + }; + + mcpServers = {}; + + extraPackages = with pkgs; [ + nodejs_22 + git + docker + ]; + + restart = "always"; + restartSec = 5; + }; +} diff --git a/hosts/netty/nginx.nix b/hosts/netty/nginx.nix index 274ecc7..874351d 100644 --- a/hosts/netty/nginx.nix +++ b/hosts/netty/nginx.nix @@ -2,7 +2,7 @@ ... }: let - openClawDomain = "netty.harivan.sh"; + hermesDomain = "netty.harivan.sh"; forgejoDomain = "git.harivan.sh"; vaultDomain = "vault.harivan.sh"; betternasDomain = "api.betternas.com"; @@ -19,7 +19,7 @@ in recommendedTlsSettings = true; clientMaxBodySize = "512m"; - virtualHosts.${openClawDomain} = { + virtualHosts.${hermesDomain} = { enableACME = true; forceSSL = true; locations."/" = { diff --git a/hosts/netty/openclaw-gateway.nix b/hosts/netty/openclaw-gateway.nix deleted file mode 100644 index 94f9a0a..0000000 --- a/hosts/netty/openclaw-gateway.nix +++ /dev/null @@ -1,68 +0,0 @@ -{ - inputs, - pkgs, - username, - ... -}: -let - homeDir = "/home/${username}"; - stateDir = "${homeDir}/.openclaw"; - runtimeConfig = "${stateDir}/openclaw.json"; -in -{ - services.openclaw-gateway = { - enable = true; - package = inputs.openClaw.packages.${pkgs.stdenv.hostPlatform.system}.default; - port = 2470; - user = username; - group = "users"; - createUser = false; - stateDir = stateDir; - environmentFiles = [ "${stateDir}/.env" ]; - environment = { - OPENCLAW_NIX_MODE = "1"; - OPENCLAW_CONFIG_PATH = runtimeConfig; - }; - execStart = "${homeDir}/.local/share/npm/bin/openclaw gateway --port 2470"; - execStartPre = [ - "+${pkgs.coreutils}/bin/install -m 600 -o ${username} -g users /etc/openclaw/openclaw.json ${runtimeConfig}" - ]; - servicePath = with pkgs; [ - pkgs.nodejs_22 - git - docker - ]; - config = { - gateway = { - mode = "local"; - bind = "loopback"; - port = 2470; - trustedProxies = [ "127.0.0.1" "::1" ]; - controlUi.allowedOrigins = [ "https://netty.harivan.sh" ]; - auth = { - mode = "token"; - token = "\${OPENCLAW_GATEWAY_TOKEN}"; - }; - }; - channels.telegram = { - botToken = "\${TELEGRAM_BOT_TOKEN}"; - dmPolicy = "pairing"; - }; - agents.defaults = { - workspace = "~/.openclaw/workspace"; - skipBootstrap = false; - model = { - primary = "opanai-codex/gpt-5.4"; - fallbacks = [ "opanai-codex/gpt-5.4-mini" ]; - }; - sandbox.mode = "off"; - }; - tools = { - profile = "coding"; - fs.workspaceOnly = true; - loopDetection.enabled = true; - deny = [ "sessions_send" "sessions_spawn" ]; - }; - }; - }; -} diff --git a/modules/hosts/netty.nix b/modules/hosts/netty.nix index 347ba36..76436a4 100644 --- a/modules/hosts/netty.nix +++ b/modules/hosts/netty.nix @@ -15,7 +15,7 @@ in specialArgs = mkSpecialArgs host; modules = [ inputs.disko.nixosModules.disko - inputs.openClaw.nixosModules.openclaw-gateway + inputs.hermes-agent.nixosModules.default ../../hosts/${host.name}/configuration.nix inputs.home-manager.nixosModules.home-manager (mkHomeManagerModule host)