{ config, lib, pkgs, agentEnvLib, ... }: let agentNames = { claude = "Claude Code"; codex = "Codex"; }; call = { claude = name: ''`${name} -p "" < /dev/null`''; codex = name: ''`${name} exec --sandbox read-only --skip-git-repo-check -o "" < /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 `, 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 ` 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 ]; }; }