{ 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.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" ]; }; }