1
0
Fork 0
forked from lukas/nixos-config
nixos-config/hosts/blunix/localServices/agent-codex.nix
2026-07-30 10:51:33 +02:00

201 lines
7 KiB
Nix

{ config, pkgs, ... }:
let
stateDir = "/var/lib/agent-codex";
roleDir = "/home/drone/nixos-config/agents";
runtimeXmppConfig = "${stateDir}/sendxmpp.conf";
runtimeBuilderXmppConfig = "${stateDir}/builder-sendxmpp.conf";
listenerScript = ./agent-codex/listener.py;
workerScript = ./agent-codex/worker.py;
builderScript = ./agent-codex/builder.py;
codexHome = "${stateDir}/codex-home";
agentHome = "${stateDir}/home";
xmppConfig = pkgs.writeText "agent-sendxmpp.conf" ''
username: agent
jserver: bot.${config.domain}
eval_password: ${pkgs.coreutils}/bin/cat ${config.age.secrets.agentXmpp.path}
'';
builderXmppConfig = pkgs.writeText "builder-sendxmpp.conf" ''
username: builder
jserver: bot.${config.domain}
eval_password: ${pkgs.coreutils}/bin/cat ${config.age.secrets.builderXmpp.path}
'';
prepareCodexHome = pkgs.writeShellScript "prepare-agent-codex-home" ''
set -eu
${pkgs.coreutils}/bin/install -d -m 700 ${codexHome} ${agentHome}
if [ ! -s ${codexHome}/auth.json ]; then
${pkgs.coreutils}/bin/install \
-m 600 \
/home/drone/.codex/auth.json \
${codexHome}/auth.json
fi
'';
commonEnvironment = {
AGENT_ALLOWED_SENDERS = "lukas@${config.domain}";
AGENT_CODEX_BIN = "${pkgs.codex}/bin/codex";
AGENT_CODEX_HOME = codexHome;
AGENT_DEBOUNCE_SECONDS = "3";
AGENT_DOMAIN = config.domain;
AGENT_FAILED_DIR = "${stateDir}/failed";
AGENT_HOME = agentHome;
AGENT_MAX_INPUT_CHARS = "20000";
AGENT_MAX_RESPONSE_CHARS = "12000";
AGENT_PROCESSING_DIR = "${stateDir}/processing";
AGENT_QUEUE_DIR = "${stateDir}/inbox";
AGENT_BUILDER_QUEUE_DIR = "${stateDir}/builder-inbox";
AGENT_BUILDER_PROCESSING_DIR = "${stateDir}/builder-processing";
AGENT_BUILDER_FAILED_DIR = "${stateDir}/builder-failed";
AGENT_ROLE_DIR = roleDir;
AGENT_BUILDER_WRITABLE_DIR = "/home/drone/nixos-config";
AGENT_SENDER_STATE_DIR = "${stateDir}/senders";
AGENT_SESSION_DIR = "${stateDir}/sessions";
AGENT_BUILDER_SENDER_STATE_DIR = "${stateDir}/builder-senders";
AGENT_BUILDER_SESSION_DIR = "${stateDir}/builder-sessions";
AGENT_SESSION_IDLE_SECONDS = "1800";
AGENT_SENDXMPP_BIN = "${pkgs.go-sendxmpp}/bin/go-sendxmpp";
AGENT_XMPP_CONFIG = runtimeXmppConfig;
AGENT_BUILDER_XMPP_CONFIG = runtimeBuilderXmppConfig;
XDG_CACHE_HOME = "${stateDir}/xdg-cache";
XDG_DATA_HOME = "${stateDir}/xdg-data";
};
in
{
systemd.services.agent-xmpp-listener = {
description = "Queue allowlisted XMPP messages for the Codex platform agent";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
environment = commonEnvironment;
serviceConfig = {
User = "drone";
Group = "users";
StateDirectory = "agent-codex";
StateDirectoryMode = "0700";
UMask = "0077";
ExecStartPre = "${pkgs.coreutils}/bin/install -m 600 ${xmppConfig} ${runtimeXmppConfig}";
ExecStart = pkgs.writeShellScript "agent-xmpp-listener" ''
set -o pipefail
${pkgs.go-sendxmpp}/bin/go-sendxmpp \
--file ${runtimeXmppConfig} \
--listen \
| ${pkgs.python3}/bin/python3 ${listenerScript}
'';
Restart = "always";
RestartSec = "10s";
NoNewPrivileges = true;
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = "read-only";
};
};
systemd.services.agent-codex-worker = {
description = "Process queued XMPP messages with the read-only Codex platform agent";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
environment = commonEnvironment;
path = [ pkgs.git pkgs.openssh ];
serviceConfig = {
Type = "oneshot";
User = "drone";
Group = "users";
WorkingDirectory = roleDir;
StateDirectory = "agent-codex";
StateDirectoryMode = "0700";
UMask = "0077";
ExecStartPre = prepareCodexHome;
ExecStart = "${pkgs.python3}/bin/python3 ${workerScript}";
TimeoutStartSec = "20min";
NoNewPrivileges = true;
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = "read-only";
};
};
systemd.paths.agent-codex-worker = {
description = "Watch the XMPP Codex inbox";
wantedBy = [ "multi-user.target" ];
pathConfig = {
PathExistsGlob = "${stateDir}/inbox/*.json";
Unit = "agent-codex-worker.service";
};
};
# Direct messages to builder@ queue the same way agent@ does, into
# builder-inbox instead of inbox.
systemd.services.agent-codex-builder-listener = {
description = "Queue allowlisted XMPP messages for the Codex Builder";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
environment = commonEnvironment // {
AGENT_QUEUE_DIR = "${stateDir}/builder-inbox";
};
serviceConfig = {
User = "drone";
Group = "users";
StateDirectory = "agent-codex";
StateDirectoryMode = "0700";
UMask = "0077";
ExecStartPre = "${pkgs.coreutils}/bin/install -m 600 ${builderXmppConfig} ${runtimeBuilderXmppConfig}";
ExecStart = pkgs.writeShellScript "agent-codex-builder-listener" ''
set -o pipefail
${pkgs.go-sendxmpp}/bin/go-sendxmpp \
--file ${runtimeBuilderXmppConfig} \
--listen \
| ${pkgs.python3}/bin/python3 ${listenerScript}
'';
Restart = "always";
RestartSec = "10s";
NoNewPrivileges = true;
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = "read-only";
};
};
# Builder converses, plans, and can now implement changes: workspace-write
# sandbox over the full nixos-config checkout (AGENT_BUILDER_WRITABLE_DIR),
# git/openssh on PATH to commit and push to dev. Deliberately no deploy
# capability -- see builder.py and agents/builder.md.
systemd.services.agent-codex-builder = {
description = "Process queued XMPP messages with the write-capable Codex Builder";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
environment = commonEnvironment;
path = [ pkgs.git pkgs.openssh pkgs.nix ];
serviceConfig = {
Type = "oneshot";
User = "drone";
Group = "users";
WorkingDirectory = roleDir;
StateDirectory = "agent-codex";
StateDirectoryMode = "0700";
UMask = "0077";
ExecStartPre = [
prepareCodexHome
"${pkgs.coreutils}/bin/install -m 600 ${builderXmppConfig} ${runtimeBuilderXmppConfig}"
];
ExecStart = "${pkgs.python3}/bin/python3 ${builderScript}";
TimeoutStartSec = "20min";
NoNewPrivileges = true;
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = "read-only";
ReadWritePaths = [ "/home/drone/nixos-config" ];
};
};
systemd.paths.agent-codex-builder = {
description = "Watch the Builder handoff inbox";
wantedBy = [ "multi-user.target" ];
pathConfig = {
PathExistsGlob = "${stateDir}/builder-inbox/*.json";
Unit = "agent-codex-builder.service";
};
};
}