402 lines
13 KiB
Nix
402 lines
13 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
inputs,
|
|
...
|
|
}: let
|
|
# Edge TTS: Seraphina — friendly, multilingual German female voice (free, no API key)
|
|
edgeVoice = "de-DE-SeraphinaMultilingualNeural";
|
|
|
|
agentSkillExclusions = {
|
|
m3ta-agents = [];
|
|
anthropic = ["pdf" "skill-creator" "xlsx"];
|
|
basecamp = [];
|
|
kestra = [];
|
|
mattpocock = ["grill-me" "caveman"];
|
|
superpowers = ["brainstorming" "systematic-debugging"];
|
|
vercel = [];
|
|
};
|
|
|
|
agentLibSharedSkillsDir = ".agents/skills";
|
|
|
|
agentLibHomeManagerStub = {lib, ...}: {
|
|
options.home.homeDirectory = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "/var/lib/hermes";
|
|
};
|
|
options.home.file = lib.mkOption {
|
|
type = lib.types.attrsOf (lib.types.submodule ({name, ...}: {
|
|
options = {
|
|
enable = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
};
|
|
executable = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.bool;
|
|
default = null;
|
|
};
|
|
force = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
};
|
|
ignorelinks = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
};
|
|
onChange = lib.mkOption {
|
|
type = lib.types.lines;
|
|
default = "";
|
|
};
|
|
recursive = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
};
|
|
source = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
};
|
|
target = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = name;
|
|
};
|
|
text = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.lines;
|
|
default = null;
|
|
};
|
|
};
|
|
}));
|
|
default = {};
|
|
};
|
|
options.home.packages = lib.mkOption {
|
|
type = lib.types.listOf lib.types.package;
|
|
default = [];
|
|
};
|
|
options.assertions = lib.mkOption {
|
|
type = lib.types.listOf lib.types.attrs;
|
|
default = [];
|
|
};
|
|
};
|
|
|
|
agentLibSourceSelections =
|
|
lib.mapAttrs (_sourceName: exclude: {
|
|
skills = {
|
|
all = true;
|
|
inherit exclude;
|
|
};
|
|
})
|
|
agentSkillExclusions;
|
|
|
|
# Evaluate agent-lib's Hermes target renderer with a minimal Home Manager
|
|
# surface, then expose its selected shared-skill outputs as a single Nix store
|
|
# directory for hermes-agent's native `skills.external_dirs` setting. The full
|
|
# Home Manager module is not imported into this NixOS host because Hermes runs
|
|
# as a system service user rather than a managed login user's Home Manager
|
|
# generation.
|
|
agentLibEval = lib.evalModules {
|
|
specialArgs = {inherit pkgs;};
|
|
modules = [
|
|
agentLibHomeManagerStub
|
|
inputs.agent-lib.homeManagerModules.default
|
|
{
|
|
home.homeDirectory = "/var/lib/hermes";
|
|
programs.agent-lib = {
|
|
enable = true;
|
|
lockFile = ../../../agent-sources.lock.json;
|
|
shared.skillsDir = agentLibSharedSkillsDir;
|
|
targets.hermes.enable = true;
|
|
profiles.default.sources = agentLibSourceSelections;
|
|
};
|
|
}
|
|
];
|
|
};
|
|
|
|
agentLibFailedAssertions = lib.filter (assertion: !assertion.assertion) agentLibEval.config.assertions;
|
|
|
|
agentLibHomeFiles =
|
|
if agentLibFailedAssertions != []
|
|
then throw (builtins.head agentLibFailedAssertions).message
|
|
else agentLibEval.config.home.file;
|
|
|
|
hermesSkillHomeFiles =
|
|
lib.filterAttrs (
|
|
targetPath: file:
|
|
lib.hasPrefix "${agentLibSharedSkillsDir}/" targetPath
|
|
&& file ? source
|
|
&& file.source != null
|
|
)
|
|
agentLibHomeFiles;
|
|
|
|
linkHermesSkill = targetPath: file: let
|
|
skillName = lib.removePrefix "${agentLibSharedSkillsDir}/" targetPath;
|
|
in ''
|
|
ln -s ${file.source} "$out"/${lib.escapeShellArg skillName}
|
|
'';
|
|
|
|
# Deterministic store renderer consumed directly by Hermes. Each entry is a
|
|
# symlink to the immutable skill directory selected by agent-lib, so
|
|
# `$out/<skill>/SKILL.md` exists without a mutable copy service.
|
|
hermesSkills =
|
|
if hermesSkillHomeFiles == {}
|
|
then throw "agent-lib: Hermes skill selection produced no skills"
|
|
else
|
|
pkgs.runCommand "hermes-agent-lib-skills" {} ''
|
|
mkdir -p $out
|
|
${lib.concatMapAttrsStringSep "\n" linkHermesSkill hermesSkillHomeFiles}
|
|
'';
|
|
in {
|
|
virtualisation.docker.enable = true;
|
|
|
|
systemd.tmpfiles.rules = [
|
|
"d /var/lib/hermes/.config 0755 hermes hermes -"
|
|
"d /var/lib/hermes/.config/tea 0755 hermes hermes -"
|
|
"L+ /var/lib/hermes/.config/tea/yml - - - - ${pkgs.writeText "tea-yml" ''
|
|
logins:
|
|
- name: m3ta
|
|
url: https://code.m3ta.dev
|
|
token:
|
|
ssh_host: code.m3ta.dev
|
|
user: m3ta-chiron
|
|
default: true
|
|
''}"
|
|
];
|
|
|
|
systemd.services.hermes-agent.restartTriggers = [hermesSkills];
|
|
|
|
services.hermes-agent = {
|
|
enable = true;
|
|
addToSystemPackages = true;
|
|
# v0.14 lazy-installs heavy optional backends by default. In the sealed
|
|
# Nix package, include the backends this host config actively uses so the
|
|
# gateway, Matrix bridge, memory, web search, TTS, and local STT work
|
|
# without runtime pip/uv mutation.
|
|
extraDependencyGroups = [
|
|
"matrix"
|
|
"honcho"
|
|
"exa"
|
|
"edge-tts"
|
|
"voice"
|
|
];
|
|
|
|
extraPackages = with pkgs; [
|
|
docker
|
|
git
|
|
curl
|
|
jq
|
|
tea
|
|
nix
|
|
python3Minimal
|
|
uv
|
|
zellij
|
|
];
|
|
|
|
# Secrets via agenix
|
|
environmentFiles = [
|
|
config.age.secrets."hermes-env".path
|
|
config.age.secrets."hermes-cloud-env".path
|
|
config.age.secrets."hermes-api-server-key".path
|
|
];
|
|
|
|
# Non-secret environment variables
|
|
# Git identity is set entirely via env vars (GIT_AUTHOR_*, GIT_COMMITTER_*,
|
|
# GIT_INIT_DEFAULT_BRANCH) — no .gitconfig file needed. Env vars take
|
|
# precedence over any gitconfig, and the hermes gateway injects them into
|
|
# all terminal sessions via .env.
|
|
environment = {
|
|
GLM_BASE_URL = "https://api.z.ai/api/coding/paas/v4/";
|
|
GIT_AUTHOR_NAME = "m3ta-chiron";
|
|
GIT_AUTHOR_EMAIL = "m3ta-chiron@agentmail.to";
|
|
GIT_COMMITTER_NAME = "m3ta-chiron";
|
|
GIT_COMMITTER_EMAIL = "m3ta-chiron@agentmail.to";
|
|
GIT_INIT_DEFAULT_BRANCH = "master";
|
|
|
|
# ── API Server (OpenAI-compatible, for Hermes Desktop App) ─────────
|
|
# Accessible via Netbird mesh VPN — not exposed to the public internet.
|
|
# Bind to 0.0.0.0 so the Netbird interface can reach it.
|
|
API_SERVER_ENABLED = "true";
|
|
API_SERVER_HOST = "0.0.0.0";
|
|
API_SERVER_PORT = toString (config.m3ta.ports.get "hermes-api");
|
|
};
|
|
|
|
# ── Container mode (podman) ──────────────────────────────────────────
|
|
container = {
|
|
enable = false;
|
|
backend = "podman";
|
|
extraVolumes = ["/home/m3tam3re/p:/projects:rw"];
|
|
extraOptions = [];
|
|
};
|
|
|
|
settings = {
|
|
# ── Model ──────────────────────────────────────────────────────────
|
|
model = {
|
|
default = "gpt-5.5";
|
|
provider = "openai-codex";
|
|
};
|
|
|
|
fallback_providers = [
|
|
{
|
|
provider = "zai";
|
|
model = "glm-5.1";
|
|
}
|
|
{
|
|
provider = "minimax";
|
|
model = "MiniMax-M2.7";
|
|
}
|
|
];
|
|
|
|
credential_pool_strategies = {
|
|
zai = "fill_first";
|
|
};
|
|
|
|
toolsets = ["all"];
|
|
|
|
# ── Agent ──────────────────────────────────────────────────────────
|
|
agent = {
|
|
max_turns = 90;
|
|
gateway_timeout = 1800;
|
|
tool_use_enforcement = "auto";
|
|
reasoning_effort = "high";
|
|
};
|
|
|
|
# ── Skills ─────────────────────────────────────────────────────────
|
|
|
|
skills = {
|
|
external_dirs = [
|
|
hermesSkills
|
|
];
|
|
};
|
|
|
|
# ── Terminal ───────────────────────────────────────────────────────
|
|
terminal = {
|
|
backend = "local";
|
|
modal_mode = "auto";
|
|
cwd = ".";
|
|
timeout = 180;
|
|
persistent_shell = true;
|
|
};
|
|
|
|
# ── Browser ────────────────────────────────────────────────────────
|
|
browser = {
|
|
inactivity_timeout = 120;
|
|
command_timeout = 30;
|
|
cloud_provider = "local";
|
|
};
|
|
|
|
# ── Checkpoints v2 ─────────────────────────────────────────────────
|
|
# v0.13.0: Single-store rewrite with real pruning + disk guardrails.
|
|
checkpoints = {
|
|
enabled = true;
|
|
max_snapshots = 50;
|
|
};
|
|
|
|
file_read_max_chars = 100000;
|
|
|
|
compression = {
|
|
enabled = true;
|
|
threshold = 0.5;
|
|
target_ratio = 0.2;
|
|
protect_last_n = 20;
|
|
};
|
|
|
|
# ── Display ────────────────────────────────────────────────────────
|
|
display = {
|
|
compact = false;
|
|
personality = "kawaii";
|
|
resume_display = "full";
|
|
busy_input_mode = "interrupt";
|
|
inline_diffs = true;
|
|
skin = "default";
|
|
tool_progress = "all";
|
|
};
|
|
|
|
# ── TTS / STT / Voice ──────────────────────────────────────────────
|
|
tts = {
|
|
provider = "edge";
|
|
edge = {
|
|
voice = edgeVoice;
|
|
};
|
|
};
|
|
|
|
stt = {
|
|
enabled = true;
|
|
provider = "local";
|
|
local = {model = "base";};
|
|
};
|
|
|
|
voice = {
|
|
record_key = "ctrl+b";
|
|
max_recording_seconds = 120;
|
|
silence_threshold = 200;
|
|
silence_duration = 3.0;
|
|
};
|
|
|
|
# ── Memory ─────────────────────────────────────────────────────────
|
|
memory = {
|
|
provider = "honcho";
|
|
memory_enabled = true;
|
|
user_profile_enabled = true;
|
|
memory_char_limit = 2200;
|
|
user_char_limit = 1375;
|
|
};
|
|
|
|
# ── Delegation / Orchestrator ────────────────────────────────────────
|
|
delegation = {
|
|
max_iterations = 50;
|
|
orchestrator_enabled = true;
|
|
max_spawn_depth = 2;
|
|
};
|
|
|
|
# ── Kanban (v0.13.0 — Multi-Agent Board) ──────────────────────────
|
|
# Durable task board with embedded dispatcher in gateway process.
|
|
# Workers are full OS processes with identity, heartbeat, reclaim,
|
|
# zombie detection, and hallucination gate.
|
|
kanban = {
|
|
dispatch_in_gateway = true;
|
|
dispatch_interval_seconds = 60;
|
|
};
|
|
|
|
# ── Matrix ────────────────────────────────────────────────────────
|
|
matrix = {
|
|
homeserver = "https://matrix.m3ta.dev";
|
|
user_id = "@chiron:m3ta.dev";
|
|
allowed_users = ["@m3tam3re:m3ta.dev"];
|
|
encryption = true;
|
|
group_sessions_per_user = true;
|
|
auto_thread = true;
|
|
dm_mention_threads = true;
|
|
};
|
|
|
|
# ── Approvals / Security ───────────────────────────────────────────
|
|
approvals = {
|
|
mode = "manual";
|
|
timeout = 60;
|
|
};
|
|
|
|
security = {
|
|
redact_secrets = true;
|
|
tirith_enabled = true;
|
|
tirith_fail_open = true;
|
|
};
|
|
|
|
# ── Cron / Session ─────────────────────────────────────────────────
|
|
cron = {wrap_response = true;};
|
|
|
|
session_reset = {
|
|
mode = "both";
|
|
idle_minutes = 1440;
|
|
at_hour = 4;
|
|
};
|
|
|
|
# ── Web ────────────────────────────────────────────────────────────
|
|
web = {backend = "exa";};
|
|
};
|
|
};
|
|
|
|
users.users.hermes = {
|
|
isNormalUser = false;
|
|
openssh.authorizedKeys.keys = [
|
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICAVF7jGP1S6vc5CxeBFD/UxiImHOgbPlKg8WYyNtOA3"
|
|
];
|
|
};
|
|
}
|