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