From 879bdb300580ad746d68dc280cf94dddf94bb020 Mon Sep 17 00:00:00 2001 From: m3ta-chiron Date: Sat, 13 Jun 2026 09:25:40 +0200 Subject: [PATCH] refactor: consume agent-lib through m3ta-home --- .a5c/inputs/fix-eval-warnings-inputs.json | 5 + .a5c/inputs/fix-eval-warnings-spec.md | 10 + .a5c/processes/fix-nix-eval-warnings.js | 301 ++++++++++++++++++++++ flake.lock | 30 +-- flake.nix | 4 +- hosts/m3-hermes/services/hermes-agent.nix | 125 +-------- 6 files changed, 339 insertions(+), 136 deletions(-) create mode 100644 .a5c/inputs/fix-eval-warnings-inputs.json create mode 100644 .a5c/inputs/fix-eval-warnings-spec.md create mode 100644 .a5c/processes/fix-nix-eval-warnings.js diff --git a/.a5c/inputs/fix-eval-warnings-inputs.json b/.a5c/inputs/fix-eval-warnings-inputs.json new file mode 100644 index 0000000..cd461ab --- /dev/null +++ b/.a5c/inputs/fix-eval-warnings-inputs.json @@ -0,0 +1,5 @@ +{ + "nixosConfigDir": "/home/m3tam3re/p/NIX/nixos-config", + "m3taHomeDir": "/home/m3tam3re/p/NIX/m3ta-home", + "specPath": "/home/m3tam3re/p/NIX/nixos-config/.a5c/inputs/fix-eval-warnings-spec.md" +} diff --git a/.a5c/inputs/fix-eval-warnings-spec.md b/.a5c/inputs/fix-eval-warnings-spec.md new file mode 100644 index 0000000..5ebcb52 --- /dev/null +++ b/.a5c/inputs/fix-eval-warnings-spec.md @@ -0,0 +1,10 @@ +Fix the following Nix/Home Manager evaluation warnings except for the gc/nh conflict warning: + +- `evaluation warning: 'system' has been renamed to/replaced by 'stdenv.hostPlatform.system'` +- `evaluation warning: m3tam3re profile: programs.ssh.matchBlocks defined in /nix/store/...-users/m3tam3re/identities/private.nix is deprecated. Use programs.ssh.settings.` + +Do not fix or change the warning: + +- `evaluation warning: programs.nh.clean.enable and nix.gc.automatic are both enabled. Please use one or the other to avoid conflict.` + +The private identity source file is in `/home/m3tam3re/p/NIX/m3ta-home/users/m3tam3re/identities/private.nix`. diff --git a/.a5c/processes/fix-nix-eval-warnings.js b/.a5c/processes/fix-nix-eval-warnings.js new file mode 100644 index 0000000..004779d --- /dev/null +++ b/.a5c/processes/fix-nix-eval-warnings.js @@ -0,0 +1,301 @@ +/** + * @process local/fix-nix-eval-warnings + * @description Fix Nix/Home Manager evaluation warnings except the nh/gc conflict warning. + * @skill systematic-debugging methodologies/superpowers/systematic-debugging.js + * @skill verification-before-completion methodologies/superpowers/verification-before-completion.js + * @skill root-cause-diagnosis methodologies/shared/root-cause-diagnosis.js + */ + +import { defineTask } from '@a5c-ai/babysitter-sdk'; + +const q = (value) => `'${String(value).replace(/'/g, `'\\''`)}'`; + +export async function process(inputs, ctx) { + const nixosConfigDir = inputs.nixosConfigDir || '/home/m3tam3re/p/NIX/nixos-config'; + const m3taHomeDir = inputs.m3taHomeDir || '/home/m3tam3re/p/NIX/m3ta-home'; + const specPath = inputs.specPath || `${nixosConfigDir}/.a5c/inputs/fix-eval-warnings-spec.md`; + + const spec = await ctx.task(readSpecTask, { specPath }); + + const inspection = await ctx.task(inspectWarningSourcesTask, { + nixosConfigDir, + m3taHomeDir, + }); + + const implementation = await ctx.task(implementFixesTask, { + nixosConfigDir, + m3taHomeDir, + spec: spec.stdout, + inspection: inspection.stdout, + }); + + const formatting = await ctx.task(formatChangedNixTask, { + m3taHomeDir, + }); + + const verification = await ctx.task(verifyWarningsTask, { + nixosConfigDir, + m3taHomeDir, + }); + + const artifacts = await ctx.task(collectArtifactsTask, { + nixosConfigDir, + m3taHomeDir, + verifyStdout: verification.stdout || '', + verifyStderr: verification.stderr || '', + }); + + const acceptance = await ctx.task(acceptanceReviewTask, { + spec: spec.stdout, + artifacts: artifacts.stdout, + }); + + if (!acceptance.accepted) { + await ctx.breakpoint({ + title: 'Warning fix acceptance review failed', + question: `Acceptance review did not approve the changes: ${acceptance.reason}`, + context: { + runId: ctx.runId, + files: [ + { path: `${m3taHomeDir}/users/m3tam3re/identities/private.nix`, format: 'nix', label: 'Private SSH identity' }, + { path: `${m3taHomeDir}/profiles/sets/coding/agents/agents.nix`, format: 'nix', label: 'Agent packages' }, + { path: `${m3taHomeDir}/profiles/contexts/desktop/default.nix`, format: 'nix', label: 'Desktop packages' }, + ], + }, + }); + } + + return { + success: acceptance.accepted, + summary: implementation.summary, + changedFiles: implementation.changedFiles, + verification: { + formatting: formatting.stdout, + warnings: verification.stdout, + review: acceptance, + }, + }; +} + +export const readSpecTask = defineTask('read-spec', (args, taskCtx) => ({ + kind: 'shell', + title: 'Read warning-fix spec', + shell: { + command: `cat ${q(args.specPath)}`, + expectedExitCode: 0, + timeout: 10000, + }, + io: { + inputJsonPath: `tasks/${taskCtx.effectId}/input.json`, + outputJsonPath: `tasks/${taskCtx.effectId}/output.json`, + }, + labels: ['spec', 'shell'], +})); + +export const inspectWarningSourcesTask = defineTask('inspect-warning-sources', (args, taskCtx) => ({ + kind: 'shell', + title: 'Inspect current warning sources', + shell: { + command: [ + 'set -euo pipefail', + `echo '== nixos-config status =='`, + `cd ${q(args.nixosConfigDir)} && git status --short`, + `echo`, + `echo '== m3ta-home status =='`, + `cd ${q(args.m3taHomeDir)} && git status --short`, + `echo`, + `echo '== active pkgs.system-style package selectors =='`, + `grep -RIn --include='*.nix' -E 'packages[.]\\$\\{pkgs[.]system\\}|packages[.]\\$\\{prev[.]system\\}|packages[.]\\$\\{final[.]system\\}' ${q(args.nixosConfigDir)} ${q(args.m3taHomeDir)} || true`, + `echo`, + `echo '== SSH matchBlocks in m3ta-home identities =='`, + `grep -RIn --include='*.nix' 'matchBlocks' ${q(`${args.m3taHomeDir}/users/m3tam3re/identities`)} || true`, + ].join('\n'), + expectedExitCode: 0, + timeout: 30000, + }, + io: { + inputJsonPath: `tasks/${taskCtx.effectId}/input.json`, + outputJsonPath: `tasks/${taskCtx.effectId}/output.json`, + }, + labels: ['diagnosis', 'shell'], +})); + +export const implementFixesTask = defineTask('implement-warning-fixes', (args, taskCtx) => ({ + kind: 'agent', + title: 'Implement requested warning fixes', + agent: { + name: 'worker', + prompt: { + role: 'Nix/Home Manager maintenance engineer', + task: 'Edit the repositories to remove the requested evaluation warnings, excluding the nh/gc warning by request.', + context: { + nixosConfigDir: args.nixosConfigDir, + m3taHomeDir: args.m3taHomeDir, + specVerbatim: args.spec, + inspectionStdout: args.inspection, + }, + instructions: [ + 'Execute the task fully; do not just provide a plan.', + 'Do not invoke the babysit skill or create another babysitter run.', + 'Read every file before editing it.', + 'Preserve unrelated existing user changes, especially any dirty files in nixos-config such as flake.nix or flake.lock.', + 'Fix active uses of pkgs.system/prev.system/final.system that trigger the Nixpkgs deprecation warning by using stdenv.hostPlatform.system through the appropriate package set.', + 'Migrate /home/m3tam3re/p/NIX/m3ta-home/users/m3tam3re/identities/private.nix from programs.ssh.matchBlocks to programs.ssh.settings.', + 'For programs.ssh.settings, use OpenSSH directive names such as HostName, User, Port, and IdentityFile; do not keep legacy camelCase option names under settings.', + 'Do not change programs.nh.clean.enable or nix.gc.automatic; the user explicitly excluded that warning.', + 'Keep the change minimal and focused on the warnings in the spec.', + 'Run a quick static check of the edited files if practical, but leave deterministic verification to the process quality gate.', + ], + outputFormat: 'JSON with summary, changedFiles, and verificationNotes.', + }, + outputSchema: { + type: 'object', + required: ['summary', 'changedFiles', 'verificationNotes'], + properties: { + summary: { type: 'string' }, + changedFiles: { type: 'array', items: { type: 'string' } }, + verificationNotes: { type: 'array', items: { type: 'string' } }, + }, + }, + }, + io: { + inputJsonPath: `tasks/${taskCtx.effectId}/input.json`, + outputJsonPath: `tasks/${taskCtx.effectId}/output.json`, + }, + labels: ['implementation', 'agent', 'nix'], +})); + +export const formatChangedNixTask = defineTask('format-changed-nix', (args, taskCtx) => ({ + kind: 'shell', + title: 'Format changed Nix files', + shell: { + command: [ + 'set -euo pipefail', + `cd ${q(args.m3taHomeDir)}`, + `if command -v alejandra >/dev/null 2>&1; then`, + ` alejandra users/m3tam3re/identities/private.nix profiles/sets/coding/agents/agents.nix profiles/contexts/desktop/default.nix`, + `else`, + ` nix run nixpkgs#alejandra -- users/m3tam3re/identities/private.nix profiles/sets/coding/agents/agents.nix profiles/contexts/desktop/default.nix`, + `fi`, + ].join('\n'), + expectedExitCode: 0, + timeout: 120000, + }, + io: { + inputJsonPath: `tasks/${taskCtx.effectId}/input.json`, + outputJsonPath: `tasks/${taskCtx.effectId}/output.json`, + }, + labels: ['format', 'shell'], +})); + +export const verifyWarningsTask = defineTask('verify-warning-removal', (args, taskCtx) => ({ + kind: 'shell', + title: 'Verify requested warnings are gone', + shell: { + command: [ + 'set -euo pipefail', + `echo '== static checks =='`, + `! grep -RIn --include='*.nix' -E 'packages[.]\\$\\{pkgs[.]system\\}|packages[.]\\$\\{prev[.]system\\}|packages[.]\\$\\{final[.]system\\}' ${q(`${args.m3taHomeDir}/profiles`)} || { echo 'Found deprecated package system selector' >&2; exit 1; }`, + `! grep -n 'matchBlocks' ${q(`${args.m3taHomeDir}/users/m3tam3re/identities/private.nix`)} || { echo 'private.nix still uses matchBlocks' >&2; exit 1; }`, + `grep -n 'settings = {' ${q(`${args.m3taHomeDir}/users/m3tam3re/identities/private.nix`)}`, + `echo`, + `echo '== nix eval m3-ares =='`, + `cd ${q(args.nixosConfigDir)}`, + `eval_stdout=$(mktemp)`, + `eval_stderr=$(mktemp)`, + `set +e`, + `nix eval .#nixosConfigurations.m3-ares.config.system.build.toplevel.drvPath --show-trace >"$eval_stdout" 2>"$eval_stderr"`, + `status=$?`, + `set -e`, + `cat "$eval_stdout"`, + `cat "$eval_stderr" >&2`, + `if [ "$status" -ne 0 ]; then exit "$status"; fi`, + `if grep -F "'system' has been renamed" "$eval_stderr"; then echo 'Deprecated system warning still present' >&2; exit 1; fi`, + `if grep -F 'programs.ssh.matchBlocks' "$eval_stderr"; then echo 'Deprecated SSH matchBlocks warning still present' >&2; exit 1; fi`, + `if grep -F 'programs.nh.clean.enable and nix.gc.automatic' "$eval_stderr" >/dev/null; then echo 'Allowed nh/gc warning remains by request.'; fi`, + ].join('\n'), + expectedExitCode: 0, + timeout: 300000, + }, + io: { + inputJsonPath: `tasks/${taskCtx.effectId}/input.json`, + outputJsonPath: `tasks/${taskCtx.effectId}/output.json`, + }, + labels: ['verification', 'shell', 'nix'], +})); + +export const collectArtifactsTask = defineTask('collect-artifacts', (args, taskCtx) => ({ + kind: 'shell', + title: 'Collect diffs and verification output', + shell: { + command: [ + 'set -euo pipefail', + `echo '== m3ta-home diff =='`, + `cd ${q(args.m3taHomeDir)} && git diff -- users/m3tam3re/identities/private.nix profiles/sets/coding/agents/agents.nix profiles/contexts/desktop/default.nix`, + `echo`, + `echo '== nixos-config diff (should not include warning fix unless needed) =='`, + `cd ${q(args.nixosConfigDir)} && git diff -- overlays/default.nix flake.nix flake.lock || true`, + `echo`, + `echo '== verification stdout =='`, + `cat <<'VERIFY_STDOUT'`, + args.verifyStdout || '', + `VERIFY_STDOUT`, + `echo`, + `echo '== verification stderr =='`, + `cat <<'VERIFY_STDERR'`, + args.verifyStderr || '', + `VERIFY_STDERR`, + ].join('\n'), + expectedExitCode: 0, + timeout: 30000, + }, + io: { + inputJsonPath: `tasks/${taskCtx.effectId}/input.json`, + outputJsonPath: `tasks/${taskCtx.effectId}/output.json`, + }, + labels: ['artifacts', 'shell'], +})); + +export const acceptanceReviewTask = defineTask('acceptance-review', (args, taskCtx) => ({ + kind: 'agent', + title: 'Review changes against requested warning fixes', + agent: { + name: 'reviewer', + prompt: { + role: 'Acceptance reviewer for a Nix/Home Manager warning fix', + task: 'Compare SPEC to ARTIFACTS directly and decide whether the requested warnings were fixed without touching the excluded nh/gc warning.', + instructions: [ + 'Ignore any narrative in your context about how ARTIFACTS were built.', + 'Do not ask for additional changes unless they are required by the SPEC.', + 'Accept if the system deprecation warning and private SSH matchBlocks warning are addressed, and the nh/gc conflict remains untouched.', + '', + 'SPEC (verbatim):', + '---', + args.spec, + '---', + '', + 'ARTIFACTS (verbatim):', + '---', + args.artifacts, + '---', + '', + 'Compare SPEC to ARTIFACTS directly. Ignore any narrative in your context about how ARTIFACTS were built.', + ], + outputFormat: 'JSON with accepted boolean, reason string, and checkedCriteria array.', + }, + outputSchema: { + type: 'object', + required: ['accepted', 'reason', 'checkedCriteria'], + properties: { + accepted: { type: 'boolean' }, + reason: { type: 'string' }, + checkedCriteria: { type: 'array', items: { type: 'string' } }, + }, + }, + }, + io: { + inputJsonPath: `tasks/${taskCtx.effectId}/input.json`, + outputJsonPath: `tasks/${taskCtx.effectId}/output.json`, + }, + labels: ['acceptance', 'agent', 'review'], +})); diff --git a/flake.lock b/flake.lock index 2cca1d7..7127f4d 100644 --- a/flake.lock +++ b/flake.lock @@ -50,17 +50,14 @@ ] }, "locked": { - "lastModified": 1780736323, - "narHash": "sha256-b4CfjbWTT+5Z0XBI2/W2DnybwkYVwLxghCwXVmw9+Iw=", - "ref": "refs/heads/master", - "rev": "b0c832c9e02d8b3d8ad091f022c859382a037afd", - "revCount": 26, - "type": "git", - "url": "ssh://gitea@code.m3ta.dev/m3tam3re/agent-lib" + "lastModified": 1781334874, + "narHash": "sha256-rGXLTREh0mvgSuYMuYf1ciEJ+ez+DyK/c7NfDgFQWEY=", + "path": "/home/m3tam3re/p/NIX/agent-lib", + "type": "path" }, "original": { - "type": "git", - "url": "ssh://gitea@code.m3ta.dev/m3tam3re/agent-lib" + "path": "/home/m3tam3re/p/NIX/agent-lib", + "type": "path" } }, "agents": { @@ -660,17 +657,14 @@ "nur": "nur" }, "locked": { - "lastModified": 1781192012, - "narHash": "sha256-zQ8AV4kiuyOT3B5bHzeF28eRv+3d3RSCIIDrTR6Rhr4=", - "ref": "refs/heads/master", - "rev": "4a9d24fe08a85d00c8e6fe0dd1d3165dc604f5fc", - "revCount": 57, - "type": "git", - "url": "ssh://gitea@code.m3ta.dev/m3tam3re/m3ta-home" + "lastModified": 1781335255, + "narHash": "sha256-GkfXjuLcZHTbSJIQuWvMy2K86l3RJ/E9iFQk3SiR66o=", + "path": "/home/m3tam3re/p/NIX/m3ta-home", + "type": "path" }, "original": { - "type": "git", - "url": "ssh://gitea@code.m3ta.dev/m3tam3re/m3ta-home" + "path": "/home/m3tam3re/p/NIX/m3ta-home", + "type": "path" } }, "m3ta-nixpkgs": { diff --git a/flake.nix b/flake.nix index 4f37a2c..59a9758 100644 --- a/flake.nix +++ b/flake.nix @@ -42,8 +42,8 @@ nix-colors.url = "github:misterio77/nix-colors"; m3ta-home = { - url = "git+ssh://gitea@code.m3ta.dev/m3tam3re/m3ta-home"; - # url = "path:/home/m3tam3re/p/NIX/m3ta-home"; + # url = "git+ssh://gitea@code.m3ta.dev/m3tam3re/m3ta-home"; + url = "path:/home/m3tam3re/p/NIX/m3ta-home"; inputs.nixpkgs.follows = "nixpkgs"; }; diff --git a/hosts/m3-hermes/services/hermes-agent.nix b/hosts/m3-hermes/services/hermes-agent.nix index 504c669..8217e88 100644 --- a/hosts/m3-hermes/services/hermes-agent.nix +++ b/hosts/m3-hermes/services/hermes-agent.nix @@ -18,66 +18,6 @@ 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 = { @@ -87,63 +27,15 @@ }) 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; - }; - } - ]; + # Deterministic store renderer consumed directly by Hermes. m3ta-home + # re-exports the focused helper so nixos-config does not need a direct + # agent-lib flake input. + hermesSkills = inputs.m3ta-home.lib.mkHermesSkillsDir { + system = pkgs.stdenv.hostPlatform.system; + name = "hermes-agent-lib-skills"; + lockFile = ../../../agent-sources.lock.json; + 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; @@ -179,6 +71,7 @@ in { ]; extraPackages = with pkgs; [ + basecamp docker git curl