~ruther/agent-environments

d024a4c805e591a0441e7de9d9bc27198fe243fb — František Boháček 2 months ago ab16fba
Add packages: tools on every launcher's PATH
4 files changed, 33 insertions(+), 3 deletions(-)

M README.md
M checks/default.nix
M modules/core.nix
M modules/launcher.nix
M README.md => README.md +2 -1
@@ 69,6 69,7 @@ Runnable examples: [`examples/basic`](examples/basic) and
| `codex.settings` | TOML linked as `$CODEX_HOME/agent-environments.config.toml` and selected with `--profile agent-environments` (layered over the account's `config.toml`). |
| `instructions.<name>.{text,order}` | Fragments of `CLAUDE.md` / `AGENTS.md`, sorted by `order` (default 1000), then name. |
| `skills.packages` | `mkSkill` packages to enable. Their skill dependencies are added automatically. |
| `packages` | Packages put on every launcher's `PATH`, e.g. tools that hooks in `claude.settings` call. |
| `modules.skills.<name>.enable` | Declared by `mkSkillModule`; adds that skill. |
| `launchers.<cmd>.{agent,id,stateDir}` | A command. `id` defaults to the command name; `stateDir` defaults to `~/.local/agent-environments/<id>`; it must be absolute or start with `~/`, and is taken literally. |
| `companions.enable` | Put every launcher of the environment on the agent's `PATH` (as companions, which cannot start companions themselves) and add a generated `companions` skill listing each one with its non-interactive call. Launchers export `AGENT_ENV_LAUNCHER` (their command name). |


@@ 86,7 87,7 @@ On every start, under a lock on the state dir:
3. Refuses to start if a managed path is occupied by something it does not own,
   such as a hand-written `CLAUDE.md`. Move it away (or turn it into an
   `instructions` fragment) and start again.
4. Sets `CLAUDE_CONFIG_DIR` / `CODEX_HOME`, puts the skills' runtime inputs on
4. Sets `CLAUDE_CONFIG_DIR` / `CODEX_HOME`, puts `packages` and the skills' runtime inputs on
   `PATH`, and runs the agent.

Launchers that share an `id` share a login and managed files. The most recent

M checks/default.nix => checks/default.nix +23 -0
@@ 156,6 156,29 @@ in
    [ "$b" = "CLAUDE_CONFIG_DIR=$HOME/.local/agent-environments/claude-pers" ] || fail "$b"
  '';

  # Hooks in agent settings call tools that no skill provides; `packages` puts
  # them on PATH, and an environment without them must not have them.
  packages-on-path =
    let
      env = mkEnvironment {
        inherit pkgs;
        modules = [
          stubs
          {
            codex.enable = true;
            packages = [ pkgs.hello ];
            launchers.codex-tools.agent = "codex";
          }
        ];
      };
    in
    runTest "packages-on-path" ''
      ${bare.launchers.codex-bare}/bin/codex-bare
      grep -qxF "HELLO=no" $STUB_LOG.env || fail "hello on PATH without packages"
      ${env.launchers.codex-tools}/bin/codex-tools
      grep -qxF "HELLO=yes" $STUB_LOG.env || fail "packages not on PATH"
    '';

  # Without settings, no flags are injected and no files are managed.
  no-settings = runTest "no-settings" ''
    ${bare.launchers.claude-work}/bin/claude-work

M modules/core.nix => modules/core.nix +7 -1
@@ 53,6 53,12 @@ in
      description = "Skills built with lib.mkSkill. Their skill dependencies are added automatically.";
    };

    packages = mkOption {
      type = types.listOf types.package;
      default = [ ];
      description = "Packages put on the PATH of every launcher, e.g. tools that hooks in the agent settings call.";
    };

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


@@ 73,7 79,7 @@ in
      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);
      runtimeInputs = lib.unique (config.packages ++ lib.concatMap (s: s.runtimeInputs) skillClosure);
    };

    assertions = lib.mapAttrsToList (name: ss: {

M modules/launcher.nix => modules/launcher.nix +1 -1
@@ 42,7 42,7 @@ let
      agent = build.agents.${launcher.agent};
      links = managedLinks agent;
      runtimePath = lib.makeBinPath (
        lib.optional (companionBin != null) companionBin ++ build.skillRuntimeInputs
        lib.optional (companionBin != null) companionBin ++ build.runtimeInputs
      );
    in
    pkgs.writeShellApplication {