210 lines
7.9 KiB
Nix
210 lines
7.9 KiB
Nix
{ pkgs, hostConfig, ... }:
|
|
|
|
{
|
|
imports = [ hostConfig.inputs.hermes-agent.nixosModules.default ];
|
|
|
|
services.hermes-agent = {
|
|
enable = true;
|
|
addToSystemPackages = true;
|
|
container.enable = false;
|
|
|
|
# Run the daemon as the same user that owns workspace files so the agent
|
|
# can read/write the project tree without permission gymnastics.
|
|
user = "googlebot";
|
|
group = "users";
|
|
createUser = false;
|
|
|
|
# Share the user's workspace tree so the agent operates on the same files
|
|
# you see when SSH'd in. Codex's workspace-write sandbox keeps writes scoped
|
|
# to this dir.
|
|
workingDirectory = "/home/googlebot/workspace";
|
|
|
|
extraPackages = with pkgs; [ nix git ripgrep fd jq codex ];
|
|
|
|
# Pulls in hindsight-client (the HTTP client lib the memory plugin uses).
|
|
extraDependencyGroups = [ "hindsight" ];
|
|
|
|
environment = {
|
|
SIGNAL_HTTP_URL = "http://127.0.0.1:8080";
|
|
CODEX_HOME = "/var/lib/hermes/.codex";
|
|
};
|
|
|
|
# Bind-mounted from /run/agenix/hermes-env on fry (host decrypts via agenix).
|
|
# Lives at /etc/... rather than /run/... because the workspace's systemd
|
|
# mounts a fresh tmpfs over /run at boot, which would shadow the incus mount.
|
|
# Codex OAuth is NOT here — it lives per-instance in /var/lib/hermes.
|
|
environmentFiles = [ "/etc/hermes-env" ];
|
|
|
|
settings = {
|
|
model = {
|
|
provider = "openai-codex";
|
|
default = "gpt-5.4";
|
|
# Delegate openai/* turns to the codex CLI subprocess so the agent gets
|
|
# codex's sandbox + tooling. Codex CLI must be on PATH and authenticated
|
|
# via `codex login` (separate from `hermes auth`).
|
|
openai_runtime = "codex_app_server";
|
|
};
|
|
toolsets = [ "all" ];
|
|
terminal.backend = "local";
|
|
|
|
# Memory lives in a sibling hindsight-api process (see systemd unit
|
|
# below) backed by system postgres. Plugin talks HTTP to it locally.
|
|
memory.provider = "hindsight";
|
|
plugins.hermes-hindsight = {
|
|
mode = "local_external";
|
|
api_url = "http://127.0.0.1:8888";
|
|
bank_id = "hermes";
|
|
memory_mode = "hybrid";
|
|
};
|
|
};
|
|
};
|
|
|
|
# Align googlebot's interactive `hermes` / `codex` invocations with the
|
|
# daemon's persisted state dirs so logins from a shell land where the
|
|
# service expects to read them.
|
|
home-manager.users.googlebot.home.sessionVariables = {
|
|
HERMES_HOME = "/var/lib/hermes/.hermes";
|
|
CODEX_HOME = "/var/lib/hermes/.codex";
|
|
};
|
|
|
|
home-manager.users.googlebot.home.packages = with pkgs; [ codex signal-cli ];
|
|
|
|
# Declarative codex CLI defaults — seeded into $CODEX_HOME on first boot,
|
|
# then codex owns the file (it rewrites config.toml on `codex login`, project
|
|
# trust grants, etc.). To re-seed after changing the defaults below, delete
|
|
# the persisted /var/lib/hermes/.codex/config.toml and restart hermes-agent.
|
|
environment.etc."hermes-codex-config.toml".text = ''
|
|
model = "gpt-5.4"
|
|
model_reasoning_effort = "medium"
|
|
sandbox_mode = "danger-full-access"
|
|
approval_policy = "never"
|
|
'';
|
|
|
|
systemd.tmpfiles.rules = [
|
|
"d /var/lib/hermes/.codex 0700 googlebot users -"
|
|
"C+ /var/lib/hermes/.codex/config.toml 0644 googlebot users - /etc/hermes-codex-config.toml"
|
|
"d /var/lib/hermes/hindsight 0700 googlebot users -"
|
|
"d /var/lib/hermes/postgresql 0750 postgres postgres -"
|
|
];
|
|
|
|
# signal-cli runs an HTTP JSON-RPC daemon on localhost; hermes-agent talks to
|
|
# it via SIGNAL_HTTP_URL. Registered as the PRIMARY device on a dedicated
|
|
# number (GV / JMP.chat / prepaid SIM) — the account identity lives entirely
|
|
# in /var/lib/hermes/signal-cli/, which is bind-mounted from the host. If
|
|
# that tree is lost, the Signal account is irrecoverable, so back it up.
|
|
systemd.services.signal-cli = {
|
|
description = "signal-cli JSON-RPC daemon for Hermes";
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [ "network-online.target" ];
|
|
wants = [ "network-online.target" ];
|
|
before = [ "hermes-agent.service" ];
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = "googlebot";
|
|
Group = "users";
|
|
ExecStart = "${pkgs.signal-cli}/bin/signal-cli --config /var/lib/hermes/signal-cli daemon --http 127.0.0.1:8080";
|
|
Restart = "always";
|
|
RestartSec = "5";
|
|
};
|
|
};
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Hindsight memory backend
|
|
#
|
|
# Architecture:
|
|
# hermes-agent --HTTP--> hindsight-api (port 8888) --asyncpg--> postgres
|
|
# hindsight-worker (background async retain/reflect)
|
|
#
|
|
# The api + worker run as googlebot with HOME=/var/lib/hermes so the
|
|
# `openai-codex` provider's hardcoded Path.home() / ".codex" / "auth.json"
|
|
# resolves to the persisted codex tokens.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
services.postgresql = {
|
|
enable = true;
|
|
package = pkgs.postgresql_17;
|
|
extensions = ps: [ ps.pgvector ];
|
|
# Persist DB state on the /var/lib/hermes bind mount so the hindsight
|
|
# memory bank survives `nixos-rebuild switch` (which recreates the
|
|
# incus container and wipes its writable layer).
|
|
dataDir = "/var/lib/hermes/postgresql/17";
|
|
ensureDatabases = [ "hindsight" ];
|
|
ensureUsers = [{
|
|
name = "googlebot";
|
|
# Superuser so the hindsight-api alembic migrations can `CREATE EXTENSION`
|
|
# and own the schema. Acceptable here because postgres is local-only to
|
|
# the sandboxed workspace and only talks to hindsight-api over the unix
|
|
# socket.
|
|
ensureClauses.superuser = true;
|
|
}];
|
|
};
|
|
|
|
systemd.services.hindsight-api =
|
|
let
|
|
env = {
|
|
# Connection — postgres is on the local unix socket; sqlalchemy gets
|
|
# an asyncpg URL via the asyncpg+host=<socket-dir> trick.
|
|
HINDSIGHT_API_DATABASE_URL = "postgresql+asyncpg:///hindsight?host=/run/postgresql";
|
|
HINDSIGHT_API_HOST = "127.0.0.1";
|
|
HINDSIGHT_API_PORT = "8888";
|
|
|
|
# LLM + embeddings both ride codex OAuth — no separate keys.
|
|
HINDSIGHT_API_LLM_PROVIDER = "openai-codex";
|
|
HINDSIGHT_API_EMBEDDINGS_PROVIDER = "openai-codex";
|
|
|
|
# We did not install [local-ml] extras, so disable the reranker
|
|
# (default is "local" which requires sentence-transformers).
|
|
HINDSIGHT_API_RERANKER_PROVIDER = "none";
|
|
|
|
# HOME drives where the codex auth file is read from
|
|
# (CodexLLM/CodexOAuthEmbeddings: Path.home() / ".codex" / "auth.json").
|
|
HOME = "/var/lib/hermes";
|
|
};
|
|
in
|
|
{
|
|
description = "Hindsight memory API server";
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [ "postgresql.service" "network-online.target" ];
|
|
wants = [ "network-online.target" ];
|
|
requires = [ "postgresql.service" ];
|
|
before = [ "hermes-agent.service" ];
|
|
environment = env;
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = "googlebot";
|
|
Group = "users";
|
|
ExecStart = "${pkgs.hindsight-api}/bin/hindsight-api";
|
|
Restart = "always";
|
|
RestartSec = "5";
|
|
WorkingDirectory = "/var/lib/hermes/hindsight";
|
|
};
|
|
};
|
|
|
|
systemd.services.hindsight-worker =
|
|
let
|
|
env = {
|
|
HINDSIGHT_API_DATABASE_URL = "postgresql+asyncpg:///hindsight?host=/run/postgresql";
|
|
HINDSIGHT_API_LLM_PROVIDER = "openai-codex";
|
|
HINDSIGHT_API_EMBEDDINGS_PROVIDER = "openai-codex";
|
|
HINDSIGHT_API_RERANKER_PROVIDER = "none";
|
|
HOME = "/var/lib/hermes";
|
|
};
|
|
in
|
|
{
|
|
description = "Hindsight background worker (async retain / reflect)";
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [ "hindsight-api.service" ];
|
|
requires = [ "hindsight-api.service" ];
|
|
environment = env;
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = "googlebot";
|
|
Group = "users";
|
|
ExecStart = "${pkgs.hindsight-api}/bin/hindsight-worker";
|
|
Restart = "always";
|
|
RestartSec = "5";
|
|
WorkingDirectory = "/var/lib/hermes/hindsight";
|
|
};
|
|
};
|
|
}
|