From 8fa9e6c0133cd776e83a9fd2b6f1ad0fb2b36bdb Mon Sep 17 00:00:00 2001 From: ethernet Date: Thu, 2 Jul 2026 19:45:09 -0400 Subject: [PATCH] fix(nix): make `hermes` in developement environment actually work install modules as editable overlay with uv --- .envrc | 2 +- nix/devShell.nix | 9 ++- nix/hermes-agent.nix | 54 +++++++++--------- nix/python.nix | 131 +++++++++++++++++++++++++++---------------- 4 files changed, 122 insertions(+), 74 deletions(-) diff --git a/.envrc b/.envrc index 01232045f16..663714366d2 100644 --- a/.envrc +++ b/.envrc @@ -1,4 +1,4 @@ -watch_file pyproject.toml uv.lock +watch_file pyproject.toml uv.lock hermes watch_file package-lock.json package.json web/package.json ui-tui/package.json website/package.json apps/shared/package.json apps/desktop/package.json ui-tui/packages/hermes-ink/package.json watch_file flake.nix flake.lock nix/devShell.nix nix/tui.nix nix/package.nix nix/python.nix nix/hermes-agent.nix nix/desktop.nix diff --git a/nix/devShell.nix b/nix/devShell.nix index 28fb380a913..b7dd91fae11 100644 --- a/nix/devShell.nix +++ b/nix/devShell.nix @@ -28,13 +28,20 @@ packages = with pkgs; [ + (pkgs.runCommand "hermes" { } '' + mkdir -p $out/bin + install -Dm755 ${../hermes} $out/bin/hermes + '') uv ] ++ self'.packages.default.passthru.devDeps; shellHook = '' - echo "Hermes Agent dev shell" ${combinedNonNpm} ${hermesNpmLib.mkNpmDevShellHook npmPackageJsonPaths} + + # for the devshell to pick up the src + export HERMES_PYTHON_SRC_ROOT=$(git rev-parse --show-toplevel) + echo "Hermes Agent dev shell in $HERMES_PYTHON_SRC_ROOT" echo "Ready. Run 'hermes' to start." ''; }; diff --git a/nix/hermes-agent.nix b/nix/hermes-agent.nix index 3d7dc260f5e..4013e80c1fe 100644 --- a/nix/hermes-agent.nix +++ b/nix/hermes-agent.nix @@ -44,7 +44,7 @@ let dependency-groups = [ "all" ] ++ extraDependencyGroups; }; - hermesVenv = mkHermesVenv extraDependencyGroups; + hermesVenv = (mkHermesVenv extraDependencyGroups).venv; hermesNpmLib = callPackage ./lib.nix { inherit npm-lockfile-fix nodejs; @@ -200,33 +200,37 @@ stdenv.mkDerivation (finalAttrs: { runHook postInstall ''; - passthru = { - inherit - hermesTui - hermesWeb - hermesNpmLib - hermesVenv - ; + passthru = + let + devPython = (mkHermesVenv (extraDependencyGroups ++ [ "dev" ])).editableVenv; + in + { + inherit + hermesTui + hermesWeb + hermesNpmLib + hermesVenv + ; - # `hermesDesktop` references `finalAttrs.finalPackage` (this whole - # derivation, after all overrides are applied) so the desktop wrapper - # can prepend its `/bin` to PATH. The desktop's resolver step 4 - # ("existing hermes on PATH") then picks up the fully wrapped - # `hermes` binary — venv with all deps, bundled skills/plugins, - # runtime PATH (ripgrep/git/ffmpeg/etc). No re-implementation - # of the agent resolution in the desktop wrapper. - hermesDesktop = callPackage ./desktop.nix { - inherit hermesNpmLib electron; - hermesAgent = finalAttrs.finalPackage; + # `hermesDesktop` references `finalAttrs.finalPackage` (this whole + # derivation, after all overrides are applied) so the desktop wrapper + # can prepend its `/bin` to PATH. The desktop's resolver step 4 + # ("existing hermes on PATH") then picks up the fully wrapped + # `hermes` binary — venv with all deps, bundled skills/plugins, + # runtime PATH (ripgrep/git/ffmpeg/etc). No re-implementation + # of the agent resolution in the desktop wrapper. + hermesDesktop = callPackage ./desktop.nix { + inherit hermesNpmLib electron; + hermesAgent = finalAttrs.finalPackage; + }; + + devShellHook = '' + export HERMES_PYTHON=${devPython}/bin/python3 + ''; + + devDeps = runtimeDeps ++ [ devPython ]; }; - devShellHook = '' - export HERMES_PYTHON=${hermesVenv}/bin/python3 - ''; - - devDeps = runtimeDeps ++ [ (mkHermesVenv (extraDependencyGroups ++ [ "dev" ])) ]; - }; - meta = with lib; { description = "AI agent with advanced tool-calling capabilities"; homepage = "https://github.com/NousResearch/hermes-agent"; diff --git a/nix/python.nix b/nix/python.nix index 16d8eaedad6..0ba5c1717ad 100644 --- a/nix/python.nix +++ b/nix/python.nix @@ -27,7 +27,8 @@ let dependency-groups = { }; }; - mkPrebuiltOverride = final: from: dependencies: + mkPrebuiltOverride = + final: from: dependencies: hacks.nixpkgsPrebuilt { inherit from; prev = { @@ -38,64 +39,100 @@ let # Legacy alibabacloud packages ship only sdists with setup.py/setup.cfg # and no pyproject.toml, so setuptools isn't declared as a build dep. - buildSystemOverrides = final: prev: builtins.mapAttrs - (name: _: prev.${name}.overrideAttrs (old: { - nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ final.setuptools ]; - })) - (lib.genAttrs [ - "alibabacloud-credentials-api" - "alibabacloud-endpoint-util" - "alibabacloud-gateway-dingtalk" - "alibabacloud-gateway-spi" - "alibabacloud-tea" - ] (_: null)); + buildSystemOverrides = + final: prev: + builtins.mapAttrs + ( + name: _: + prev.${name}.overrideAttrs (old: { + nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ final.setuptools ]; + }) + ) + ( + lib.genAttrs [ + "alibabacloud-credentials-api" + "alibabacloud-endpoint-util" + "alibabacloud-gateway-dingtalk" + "alibabacloud-gateway-spi" + "alibabacloud-tea" + ] (_: null) + ); - pythonPackageOverrides = final: _prev: - if isAarch64Darwin then { - numpy = mkPrebuiltOverride final python312.pkgs.numpy { }; + pythonPackageOverrides = + final: _prev: + if isAarch64Darwin then + { + numpy = mkPrebuiltOverride final python312.pkgs.numpy { }; - pyarrow = mkPrebuiltOverride final python312.pkgs.pyarrow { }; + pyarrow = mkPrebuiltOverride final python312.pkgs.pyarrow { }; - av = mkPrebuiltOverride final python312.pkgs.av { }; + av = mkPrebuiltOverride final python312.pkgs.av { }; - humanfriendly = mkPrebuiltOverride final python312.pkgs.humanfriendly { }; + humanfriendly = mkPrebuiltOverride final python312.pkgs.humanfriendly { }; - coloredlogs = mkPrebuiltOverride final python312.pkgs.coloredlogs { - humanfriendly = [ ]; - }; + coloredlogs = mkPrebuiltOverride final python312.pkgs.coloredlogs { + humanfriendly = [ ]; + }; - onnxruntime = mkPrebuiltOverride final python312.pkgs.onnxruntime { - coloredlogs = [ ]; - numpy = [ ]; - packaging = [ ]; - }; + onnxruntime = mkPrebuiltOverride final python312.pkgs.onnxruntime { + coloredlogs = [ ]; + numpy = [ ]; + packaging = [ ]; + }; - ctranslate2 = mkPrebuiltOverride final python312.pkgs.ctranslate2 { - numpy = [ ]; - pyyaml = [ ]; - }; + ctranslate2 = mkPrebuiltOverride final python312.pkgs.ctranslate2 { + numpy = [ ]; + pyyaml = [ ]; + }; - faster-whisper = mkPrebuiltOverride final python312.pkgs.faster-whisper { - av = [ ]; - ctranslate2 = [ ]; - huggingface-hub = [ ]; - onnxruntime = [ ]; - tokenizers = [ ]; - tqdm = [ ]; - }; - } else {}; + faster-whisper = mkPrebuiltOverride final python312.pkgs.faster-whisper { + av = [ ]; + ctranslate2 = [ ]; + huggingface-hub = [ ]; + onnxruntime = [ ]; + tokenizers = [ ]; + tqdm = [ ]; + }; + } + else + { }; pythonSet = (callPackage pyproject-nix.build.packages { python = python312; }).overrideScope - (lib.composeManyExtensions [ - pyproject-build-systems.overlays.default - overlay - buildSystemOverrides - pythonPackageOverrides - ]); + ( + lib.composeManyExtensions [ + pyproject-build-systems.overlays.default + overlay + buildSystemOverrides + pythonPackageOverrides + ] + ); + + editableOverlay = workspace.mkEditablePyprojectOverlay { + root = "$HERMES_PYTHON_SRC_ROOT"; # resolved at shellHook time + }; + + workspaceRoot = ./..; + editableSet = pythonSet.overrideScope ( + lib.composeManyExtensions [ + editableOverlay + (final: prev: { + hermes-agent = prev.hermes-agent.overrideAttrs (old: { + # point straight at the real source instead of the filtered nix store copy + src = workspaceRoot; + nativeBuildInputs = old.nativeBuildInputs ++ final.resolveBuildSystem { editables = [ ]; }; + }); + }) + ] + ); in -pythonSet.mkVirtualEnv "hermes-agent-env" { - hermes-agent = dependency-groups; +{ + venv = pythonSet.mkVirtualEnv "hermes-agent-env" { + hermes-agent = dependency-groups; + }; + editableVenv = editableSet.mkVirtualEnv "hermes-agent-editable-env" { + hermes-agent = dependency-groups; + }; }