~ruther/agent-environments

5ce10e8e25815ef82f388e06d4aa002273ac29c2 — František Boháček 2 months ago 3e0d1cb
Add core module: instruction fragments and skill closure
1 files changed, 86 insertions(+), 0 deletions(-)

A modules/core.nix
A modules/core.nix => modules/core.nix +86 -0
@@ 0,0 1,86 @@
{
  config,
  lib,
  pkgs,
  agentEnvLib,
  ...
}:
let
  inherit (lib) mkOption types;

  sortedInstructions = lib.sort (
    a: b: if a.order == b.order then a.name < b.name else a.order < b.order
  ) (lib.mapAttrsToList (name: v: v // { inherit name; }) config.instructions);

  # Transitive closure over skillDeps, keyed by store path.
  skillClosure = map (e: e.skill) (
    builtins.genericClosure {
      startSet = map (s: {
        key = s.outPath;
        skill = s;
      }) config.skills.packages;
      operator =
        e:
        map (s: {
          key = s.outPath;
          skill = s;
        }) e.skill.skillDeps;
    }
  );
  skillsByName = lib.groupBy (s: s.skillName) skillClosure;
in
{
  options = {
    instructions = mkOption {
      default = { };
      description = "Fragments of the instruction file (CLAUDE.md / AGENTS.md), sorted by order, then name.";
      type = types.attrsOf (
        types.submodule {
          options = {
            text = mkOption { type = types.lines; };
            order = mkOption {
              type = types.int;
              default = 1000;
            };
          };
        }
      );
    };

    skills.packages = mkOption {
      type = types.listOf types.package;
      default = [ ];
      description = "Skills built with lib.mkSkill. Their skill dependencies are added automatically.";
    };

    # Read-only results used by the agent and launcher modules.
    build = mkOption {
      type = types.attrsOf types.anything;
      default = { };
      internal = true;
    };

    assertions = mkOption {
      type = types.listOf types.attrs;
      default = [ ];
      internal = true;
    };

  };

  config = {
    build = {
      instructionsText =
        lib.concatMapStringsSep "\n\n" (i: lib.removeSuffix "\n" i.text) sortedInstructions + "\n";
      skills = lib.mapAttrsToList (_: ss: lib.head ss) skillsByName;
      skillRuntimeInputs = lib.unique (lib.concatMap (s: s.runtimeInputs) skillClosure);
    };

    assertions = lib.mapAttrsToList (name: ss: {
      assertion = lib.length ss == 1;
      message = "skill name '${name}' is provided by different packages: ${
        lib.concatMapStringsSep ", " (s: s.outPath) ss
      }";
    }) skillsByName;
  };
}