mirror of
https://github.com/0xrsydn/nix-hermes-agent.git
synced 2026-08-07 00:53:52 +00:00
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:
commit
f70dc0f39b
6 changed files with 468 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
result
|
||||||
|
result-*
|
||||||
|
.direnv
|
||||||
75
README.md
Normal file
75
README.md
Normal 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
61
flake.lock
generated
Normal 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
34
flake.nix
Normal 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
125
module.nix
Normal 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
170
package.nix
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue