{ description = "Reusable jailed LLM agents (opencode)"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; jail-nix.url = "sourcehut:~alexdavid/jail.nix"; llm-agents.url = "github:numtide/llm-agents.nix"; flake-utils.url = "github:numtide/flake-utils"; }; outputs = { self, nixpkgs, flake-utils, jail-nix, llm-agents, ... }: flake-utils.lib.eachDefaultSystem ( system: let pkgs = nixpkgs.legacyPackages.${system}; jail = jail-nix.lib.init pkgs; defaultPackages = with pkgs; [ bashInteractive curl wget jq git which ripgrep gnugrep gawkInteractive ps findutils gzip unzip gnutar diffutils ]; makeJailedConfig = { name, jail, pkgs, extraPkgs ? [ ], extraCombinators ? [ ], mountCwd ? true, }: with jail.combinators; ( [ network time-zone no-new-session ] ++ (if mountCwd then [ mount-cwd ] else [ ]) ++ [ (readwrite (noescape "~/.config/${name}")) (readwrite (noescape "~/.local/share/${name}")) (readwrite (noescape "~/.local/state/${name}")) (add-pkg-deps defaultPackages) (add-pkg-deps extraPkgs) ] ++ extraCombinators ); makeJailedAgent = { name, pkg, extraPkgs ? [ ], extraCombinators ? [ ], mountCwd ? true, }: jail name pkg (makeJailedConfig { inherit name jail pkgs extraPkgs extraCombinators mountCwd; }); # CWD-mounted variants opencode-jailed = makeJailedAgent { name = "opencode"; pkg = llm-agents.packages.${system}.opencode; }; claude-jailed = makeJailedAgent { name = "claude-code"; pkg = llm-agents.packages.${system}.claude-code; }; # No-CWD variants opencode-jailed-nocwd = makeJailedAgent { name = "opencode-nocwd"; pkg = llm-agents.packages.${system}.opencode; mountCwd = false; }; claude-jailed-nocwd = makeJailedAgent { name = "claude-code-nocwd"; pkg = llm-agents.packages.${system}.claude-code; mountCwd = false; }; lib = { # Call as: inputs.jailed-agents.lib.makeJailed system { agentTool = "opencode"; extraPkgs = [...]; mountCwd = false; } makeJailed = system': { agentTool, extraPkgs ? [ ], extraCombinators ? [ ], mountCwd ? true, }: let pkgs' = nixpkgs.legacyPackages.${system'}; jail' = jail-nix.lib.init pkgs'; pkg' = llm-agents.packages.${system'}.opencode; defaultPackages' = with pkgs'; [ bashInteractive curl wget jq git which ripgrep gnugrep gawkInteractive ps findutils gzip unzip gnutar diffutils ]; makeJailedConfig' = { name, jail, pkgs, extraPkgs ? [ ], extraCombinators ? [ ], mountCwd ? true, }: with jail'.combinators; ( [ network time-zone no-new-session ] ++ (if mountCwd then [ mount-cwd ] else [ ]) ++ [ (readwrite (noescape "~/.config/${name}")) (readwrite (noescape "~/.local/share/${name}")) (readwrite (noescape "~/.local/state/${name}")) (add-pkg-deps defaultPackages') (add-pkg-deps extraPkgs) ] ++ extraCombinators ); in jail' agentTool pkg' (makeJailedConfig' { name = agentTool; jail = jail'; pkgs = pkgs'; inherit extraPkgs extraCombinators mountCwd; }); }; test-agent-basic = lib.makeJailed system { agentTool = "test-agent"; }; test-agent-extra = lib.makeJailed system { agentTool = "test-agent-extra"; extraPkgs = [ pkgs.hello ]; }; in { packages = { inherit opencode-jailed claude-jailed opencode-jailed-nocwd claude-jailed-nocwd ; }; checks = { opencode-jailed-build = pkgs.runCommand "test-opencode-jailed" { buildInputs = [ opencode-jailed ]; } '' test -f ${opencode-jailed}/bin/opencode touch $out ''; claude-jailed-build = pkgs.runCommand "test-claude-jailed" { buildInputs = [ claude-jailed ]; } '' test -f ${claude-jailed}/bin/claude-code touch $out ''; opencode-jailed-nocwd-build = pkgs.runCommand "test-opencode-jailed-nocwd" { buildInputs = [ opencode-jailed-nocwd ]; } '' test -f ${opencode-jailed-nocwd}/bin/opencode-nocwd touch $out ''; claude-jailed-nocwd-build = pkgs.runCommand "test-claude-jailed-nocwd" { buildInputs = [ claude-jailed-nocwd ]; } '' test -f ${claude-jailed-nocwd}/bin/claude-code-nocwd touch $out ''; lib-makeJailed-basic = pkgs.runCommand "test-lib-makeJailed-basic" { buildInputs = [ test-agent-basic ]; } '' test -f ${test-agent-basic}/bin/test-agent touch $out ''; lib-makeJailed-with-extraPkgs = pkgs.runCommand "test-lib-makeJailed-extraPkgs" { buildInputs = [ test-agent-extra ]; } '' test -f ${test-agent-extra}/bin/test-agent-extra test -d ${pkgs.hello} touch $out ''; test-opencode-tools = pkgs.runCommand "test-opencode-tools" { buildInputs = [ opencode-jailed ]; } '' test -f ${opencode-jailed}/bin/opencode touch $out ''; test-claude-tools = pkgs.runCommand "test-claude-tools" { buildInputs = [ claude-jailed ]; } '' test -f ${claude-jailed}/bin/claude-code touch $out ''; }; lib = { # Call as: inputs.jailed-agents.lib.makeJailed system { agentTool = "opencode"; extraPkgs = [...]; mountCwd = false; } makeJailed = system': { agentTool, extraPkgs ? [ ], extraCombinators ? [ ], mountCwd ? true, }: let pkgs' = nixpkgs.legacyPackages.${system'}; jail' = jail-nix.lib.init pkgs'; pkg' = llm-agents.packages.${system'}.opencode; defaultPackages' = with pkgs'; [ bashInteractive curl wget jq git which ripgrep gnugrep gawkInteractive ps findutils gzip unzip gnutar diffutils ]; makeJailedConfig' = { name, jail, pkgs, extraPkgs ? [ ], extraCombinators ? [ ], mountCwd ? true, }: with jail'.combinators; ( [ network time-zone no-new-session ] ++ (if mountCwd then [ mount-cwd ] else [ ]) ++ [ (readwrite (noescape "~/.config/${name}")) (readwrite (noescape "~/.local/share/${name}")) (readwrite (noescape "~/.local/state/${name}")) (add-pkg-deps defaultPackages') (add-pkg-deps extraPkgs) ] ++ extraCombinators ); in jail' agentTool pkg' (makeJailedConfig' { name = agentTool; jail = jail'; pkgs = pkgs'; inherit extraPkgs extraCombinators mountCwd; }); }; } ); }