~ruther/agent-environments

ref: d024a4c805e591a0441e7de9d9bc27198fe243fb agent-environments/modules/companions.nix -rw-r--r-- 3.4 KiB
d024a4c8 — František Boháček Add packages: tools on every launcher's PATH 2 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
{
  config,
  lib,
  pkgs,
  agentEnvLib,
  ...
}:
let
  agentNames = {
    claude = "Claude Code";
    codex = "Codex";
  };
  call = {
    claude = name: ''`${name} -p "<prompt>" < /dev/null`'';
    codex =
      name:
      ''`${name} exec --sandbox read-only --skip-git-repo-check -o <answer-file> "<prompt>" < /dev/null`'';
  };
  rows = lib.concatStrings (
    lib.mapAttrsToList (
      name: l: "| `${name}` | ${agentNames.${l.agent}} | `${l.id}` | ${call.${l.agent} name} |\n"
    ) config.launchers
  );

  skill = agentEnvLib.mkSkill {
    inherit pkgs;
    name = "companions";
    src = pkgs.writeTextDir "SKILL.md" ''
      ---
      name: companions
      description: Use when you need a companion agent - a second opinion, a reviewer, a delegated task - when the user names one or a fallback order ("use claude work as your companion, fall back to codex work"), or when a companion run hits a usage limit.
      ---

      # Companion agents

      Each command below starts another agent under its own login (profile) and
      prints its answer. `$AGENT_ENV_LAUNCHER` names the profile you are running
      as. Run companions unsandboxed: they need their own state dir and the network.

      | Command | Agent | Profile | Non-interactive call |
      |---|---|---|---|
      ${rows}
      ## Choosing a companion

      1. The user named companions or an order ("claude work, then codex work"):
         use exactly that, in that order.
      2. Otherwise: the other agent family than yours. Among those, prefer the one
         on the same account as yours (compare the command names); else the first
         listed.

      ## Calling

      - **Claude Code** (`-p`): prints the final answer on stdout. Tools that need
        approval are refused in this mode, so it is effectively read-only. Allow
        specific commands with `--allowedTools 'Bash(git diff:*)'` when needed.
      - **Codex** (`exec`): the final answer goes to `-o <answer-file>`, the
        transcript to stderr. Keep `< /dev/null`: with stdin open it waits forever
        ("Reading additional input from stdin...").
      - Long runs: start them in the background, redirect stderr to a log, and
        read the answer when the command exits.

      ## Usage limits and fallback

      A companion is limited when it exits non-zero and its output talks about a
      usage or rate limit ("usage limit", "limit reached", "hit your limit").
      Then:

      1. Record which companion was limited, and the reset time if it says one.
      2. Send the same prompt to the next companion in the order; if there is no
         order, use the next choice from "Choosing a companion".
      3. Do not retry a limited companion in the same task unless its reset time
         has passed.
      4. When every companion is limited, say so and follow the calling skill's
         rule for an unreachable companion.

      Always report which companion actually answered.

      ## Nesting

      A companion session does not have these commands on its `PATH`. If
      `command -v <command>` finds nothing, you are running as a companion
      yourself: do not start further companions.
    '';
  };
in
{
  options.companions.enable = lib.mkEnableOption ''
    companions: put every launcher of this environment on the agent's PATH (without
    companions of their own) and add a generated `companions` skill describing them'';

  config = lib.mkIf config.companions.enable {
    skills.packages = [ skill ];
  };
}