feat: initial nix package and NixOS module for hermes-agent v0.2.0

- buildPythonApplication with Python 3.12
- All extras included (messaging, cron, tts, voice, mcp, honcho, acp)
- Custom PyPI packages: fal-client, honcho-ai, agent-client-protocol
- NixOS module with systemd service for gateway mode
- Runtime deps wrapped: nodejs 22, ripgrep, ffmpeg, git
- Flake-based with overlay support
This commit is contained in:
Ciphercat 2026-03-15 23:49:39 +00:00
commit f70dc0f39b
6 changed files with 468 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
result
result-*
.direnv

75
README.md Normal file
View file

@ -0,0 +1,75 @@
# nix-hermes
Nix package and NixOS module for [Hermes Agent](https://github.com/NousResearch/hermes-agent) by Nous Research.
## Quick Start
### Flake usage
```nix
{
inputs.nix-hermes.url = "github:0xrsydn/nix-hermes";
outputs = { self, nixpkgs, nix-hermes, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
modules = [
nix-hermes.nixosModules.hermes-agent
{
services.hermes-agent = {
enable = true;
environmentFile = "/run/secrets/hermes-env"; # API keys
};
}
];
};
};
}
```
### Just the package
```bash
nix run github:0xrsydn/nix-hermes -- --help
```
### NixOS module options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enable` | bool | `false` | Enable the Hermes Agent gateway service |
| `user` | string | `"hermes"` | Service user |
| `group` | string | `"hermes"` | Service group |
| `homeDir` | path | `/var/lib/hermes` | State directory |
| `workDir` | path | `${homeDir}/workspace` | Working directory |
| `environmentFile` | path | `null` | Secrets file (API keys, tokens) |
| `extraEnvironment` | attrs | `{}` | Extra env vars |
| `extraArgs` | list | `[]` | Extra CLI args for `hermes gateway` |
| `extraPackages` | list | `[]` | Extra packages on PATH |
### Environment file example
```bash
ANTHROPIC_API_KEY=sk-ant-...
OPENROUTER_API_KEY=sk-or-...
TELEGRAM_TOKEN=123456:ABC...
OPENAI_API_KEY=sk-...
```
## What's included
- **`hermes`** — Interactive CLI
- **`hermes-agent`** — Agent runner
- **`hermes-acp`** — ACP adapter
- **NixOS module** — systemd service for gateway mode
- Runtime deps wrapped: Node.js 22, ripgrep, ffmpeg, git
## Development
```bash
nix develop # Enter dev shell with hermes on PATH
nix build # Build the package
```
## License
MIT (same as upstream)

61
flake.lock generated Normal file
View file

@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"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"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1773389992,
"narHash": "sha256-wvfdLLWJ2I9oEpDd9PfMA8osfIZicoQ5MT1jIwNs9Tk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c06b4ae3d6599a672a6210b7021d699c351eebda",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

34
flake.nix Normal file
View file

@ -0,0 +1,34 @@
{
description = "Nix package and NixOS module for Hermes Agent by Nous Research";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
in
{
packages = {
hermes-agent = pkgs.callPackage ./package.nix { };
default = self.packages.${system}.hermes-agent;
};
devShells.default = pkgs.mkShell {
packages = [ self.packages.${system}.hermes-agent ];
};
}
) // {
nixosModules = {
hermes-agent = import ./module.nix self;
default = self.nixosModules.hermes-agent;
};
overlays.default = final: prev: {
hermes-agent = final.callPackage ./package.nix { };
};
};
}

125
module.nix Normal file
View file

@ -0,0 +1,125 @@
self:
{ config, lib, pkgs, ... }:
let
cfg = config.services.hermes-agent;
hermes-agent = self.packages.${pkgs.system}.hermes-agent;
in
{
options.services.hermes-agent = {
enable = lib.mkEnableOption "Hermes Agent gateway service";
package = lib.mkOption {
type = lib.types.package;
default = hermes-agent;
description = "The hermes-agent package to use.";
};
user = lib.mkOption {
type = lib.types.str;
default = "hermes";
description = "User under which Hermes Agent runs.";
};
group = lib.mkOption {
type = lib.types.str;
default = "hermes";
description = "Group under which Hermes Agent runs.";
};
homeDir = lib.mkOption {
type = lib.types.path;
default = "/var/lib/hermes";
description = "Home directory for Hermes Agent state.";
};
workDir = lib.mkOption {
type = lib.types.path;
default = "${cfg.homeDir}/workspace";
defaultText = lib.literalExpression ''"''${cfg.homeDir}/workspace"'';
description = "Working directory for Hermes Agent.";
};
environmentFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = ''
Path to an environment file containing secrets (API keys, tokens).
This file should contain lines like:
ANTHROPIC_API_KEY=sk-...
TELEGRAM_TOKEN=...
OPENROUTER_API_KEY=...
'';
};
extraEnvironment = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = { };
description = "Extra environment variables for the Hermes Agent service.";
example = lib.literalExpression ''
{
HERMES_DEFAULT_MODEL = "anthropic/claude-sonnet-4-20250514";
}
'';
};
extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Extra command-line arguments to pass to hermes gateway.";
};
extraPackages = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = [ ];
description = "Extra packages to make available on PATH.";
};
};
config = lib.mkIf cfg.enable {
users.users.${cfg.user} = {
isSystemUser = true;
group = cfg.group;
home = cfg.homeDir;
createHome = true;
description = "Hermes Agent service user";
};
users.groups.${cfg.group} = { };
systemd.services.hermes-agent = {
description = "Hermes Agent Gateway";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
environment = {
HOME = cfg.homeDir;
HERMES_HOME = "${cfg.homeDir}/.hermes";
} // cfg.extraEnvironment;
serviceConfig = {
Type = "simple";
User = cfg.user;
Group = cfg.group;
WorkingDirectory = cfg.workDir;
ExecStart = lib.concatStringsSep " " ([
"${cfg.package}/bin/hermes"
"gateway"
] ++ cfg.extraArgs);
Restart = "on-failure";
RestartSec = 5;
# Hardening
NoNewPrivileges = true;
ProtectSystem = "strict";
ProtectHome = false;
ReadWritePaths = [ cfg.homeDir ];
PrivateTmp = true;
} // lib.optionalAttrs (cfg.environmentFile != null) {
EnvironmentFile = cfg.environmentFile;
};
path = [ cfg.package ] ++ cfg.extraPackages;
};
};
}

170
package.nix Normal file
View file

@ -0,0 +1,170 @@
{ lib
, python312Packages
, python312
, fetchFromGitHub
, fetchPypi
, makeWrapper
, nodejs_22
, ripgrep
, ffmpeg
, git
}:
let
python = python312;
pythonPackages = python312Packages;
# --- Missing PyPI packages ---
fal-client = pythonPackages.buildPythonPackage rec {
pname = "fal-client";
version = "0.13.1";
pyproject = true;
src = fetchPypi {
pname = "fal_client";
inherit version;
hash = "sha256-nhwH0KYbRSqP+0jBmd5fJUPXVG8SMPYxI3BEMSfF6Tc=";
};
build-system = with pythonPackages; [ setuptools setuptools-scm ];
dependencies = with pythonPackages; [
httpx
httpx-sse
msgpack
websockets
];
doCheck = false;
pythonImportsCheck = [ "fal_client" ];
};
honcho-ai = pythonPackages.buildPythonPackage rec {
pname = "honcho-ai";
version = "2.0.1";
pyproject = true;
src = fetchPypi {
pname = "honcho_ai";
inherit version;
hash = "sha256-b97r+UVOYrxSPVeIjlA1nme6r9sh9oYh+cFOCNwAYjo=";
};
build-system = with pythonPackages; [ setuptools wheel ];
dependencies = with pythonPackages; [
httpx
pydantic
typing-extensions
];
doCheck = false;
pythonImportsCheck = [ "honcho" ];
};
agent-client-protocol = pythonPackages.buildPythonPackage rec {
pname = "agent-client-protocol";
version = "0.8.1";
pyproject = true;
src = fetchPypi {
pname = "agent_client_protocol";
inherit version;
hash = "sha256-G78VZjv1H2SUJZf2OOMqYoTF2pGAVdlnLTUQ6WUUPb0=";
};
build-system = [ pythonPackages.pdm-backend ];
dependencies = with pythonPackages; [
pydantic
];
doCheck = false;
pythonImportsCheck = [ "acp" ];
};
version = "0.2.0";
rev = "64d333204bb2e32cc90a58b5ec5a4db127396dfc";
src = fetchFromGitHub {
owner = "NousResearch";
repo = "hermes-agent";
inherit rev;
hash = "sha256-Li8jPEFDthj/AKmlwJhLWxItc34qcTrmJUDQ4kaSxVg=";
fetchSubmodules = true;
};
in
pythonPackages.buildPythonApplication {
pname = "hermes-agent";
inherit version src;
pyproject = true;
build-system = [ pythonPackages.setuptools ];
dependencies = with pythonPackages; [
# Core
openai
anthropic
python-dotenv
fire
httpx
rich
tenacity
pyyaml
requests
jinja2
pydantic
prompt-toolkit
# Tools
firecrawl-py
fal-client
# TTS
edge-tts
faster-whisper
# mini-swe-agent deps
litellm
typer
platformdirs
# Skills Hub
pyjwt
# Messaging
python-telegram-bot
discordpy
aiohttp
slack-bolt
slack-sdk
# Cron
croniter
# CLI
simple-term-menu
# TTS premium
elevenlabs
# Voice
sounddevice
numpy
# PTY
ptyprocess
# Honcho
honcho-ai
# MCP
mcp
# ACP
agent-client-protocol
];
nativeBuildInputs = [ makeWrapper ];
# Don't run tests during build
doCheck = false;
# mini-swe-agent is included via fetchSubmodules and referenced at runtime
postFixup = ''
# Wrap binaries with runtime deps on PATH
for bin in $out/bin/hermes $out/bin/hermes-agent $out/bin/hermes-acp; do
if [ -f "$bin" ]; then
wrapProgram "$bin" \
--prefix PATH : ${lib.makeBinPath [ nodejs_22 ripgrep ffmpeg git ]}
fi
done
'';
meta = with lib; {
description = "The self-improving AI agent by Nous Research";
homepage = "https://github.com/NousResearch/hermes-agent";
license = licenses.mit;
maintainers = [ ];
mainProgram = "hermes";
platforms = platforms.linux ++ platforms.darwin;
};
}