diff --git a/flake.lock b/flake.lock index ea38c54..f6f514a 100644 --- a/flake.lock +++ b/flake.lock @@ -14,9 +14,81 @@ "url": "https://flakehub.com/f/NixOS/nixpkgs/0" } }, + "pyproject-build-systems": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "pyproject-nix": [ + "pyproject-nix" + ], + "uv2nix": [ + "uv2nix" + ] + }, + "locked": { + "lastModified": 1782093830, + "narHash": "sha256-6gmEVe69+KlRkZD4PEEV5xAlB9CB0Y9TiuEgQjDrKTQ=", + "owner": "pyproject-nix", + "repo": "build-system-pkgs", + "rev": "430680a19bc85a3bda55f12e4cc1a1aadcf2e478", + "type": "github" + }, + "original": { + "owner": "pyproject-nix", + "repo": "build-system-pkgs", + "type": "github" + } + }, + "pyproject-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1782089418, + "narHash": "sha256-LRD1SuQWr49fGq3A+8GLXfsLE2xqIpQA440YDZwms3M=", + "owner": "pyproject-nix", + "repo": "pyproject.nix", + "rev": "43f0b40edd0a74c63f66b7b48d969ae6b740d611", + "type": "github" + }, + "original": { + "owner": "pyproject-nix", + "repo": "pyproject.nix", + "type": "github" + } + }, "root": { "inputs": { - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "pyproject-build-systems": "pyproject-build-systems", + "pyproject-nix": "pyproject-nix", + "uv2nix": "uv2nix" + } + }, + "uv2nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "pyproject-nix": [ + "pyproject-nix" + ] + }, + "locked": { + "lastModified": 1782100052, + "narHash": "sha256-UfyLY3Hfwb3JqxCcj0953GxTFI5dEL85EKEe6DAFiVs=", + "owner": "pyproject-nix", + "repo": "uv2nix", + "rev": "920fc6dfaf9f10ec56de93b184055e1a9f380d5e", + "type": "github" + }, + "original": { + "owner": "pyproject-nix", + "repo": "uv2nix", + "type": "github" } } }, diff --git a/flake.nix b/flake.nix index 0354f7d..569aae1 100644 --- a/flake.nix +++ b/flake.nix @@ -1,10 +1,31 @@ { description = "Tartarus: a Nix-defined containment runtime for auditable agents"; - inputs.nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0"; + inputs = { + nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0"; + + # uv2nix turns uv.lock into a Nix Python package set, so the harness's Python + # deps are resolved from the lockfile rather than hand-maintained. + pyproject-nix.url = "github:pyproject-nix/pyproject.nix"; + pyproject-nix.inputs.nixpkgs.follows = "nixpkgs"; + uv2nix.url = "github:pyproject-nix/uv2nix"; + uv2nix.inputs.pyproject-nix.follows = "pyproject-nix"; + uv2nix.inputs.nixpkgs.follows = "nixpkgs"; + # uv does not lock build systems; this overlay supplies backends (hatchling). + pyproject-build-systems.url = "github:pyproject-nix/build-system-pkgs"; + pyproject-build-systems.inputs.pyproject-nix.follows = "pyproject-nix"; + pyproject-build-systems.inputs.uv2nix.follows = "uv2nix"; + pyproject-build-systems.inputs.nixpkgs.follows = "nixpkgs"; + }; outputs = - { nixpkgs, ... }: + { + nixpkgs, + pyproject-nix, + uv2nix, + pyproject-build-systems, + ... + }: let supportedSystems = [ "x86_64-linux" @@ -34,13 +55,28 @@ agentsLib.mkAgents { inherit pkgs packages; } (import ./agent.nix { inherit pkgs; }) ); - # The developer shell for hacking on this harness (Python + pytest). This is - # distinct from an agent's own `shell`, whose PATH is baked into its bundle. - devShells = eachSystem ( + # The packaged harness (uv2nix virtualenv). `nix build .#tartarus` / + # `nix run .#tartarus -- "prompt"`. See package.nix. + packages = eachSystem ( system: let pkgs = pkgsFor system; + tartarus = pkgs.callPackage ./package.nix { + inherit pyproject-nix uv2nix pyproject-build-systems; + }; in + { + default = tartarus; + inherit tartarus; + } + ); + + # The developer shell for hacking on this harness (Python + pytest). This + # is distinct from an agent's own `shell`, whose PATH is baked into its + # bundle. ruff and ty are supplied by `uv`, not this shell. + devShells = eachSystem ( + system: + let pkgs = pkgsFor system; in { default = pkgs.mkShellNoCC { packages = with pkgs; [ @@ -52,10 +88,10 @@ nixfmt ripgrep gnused - (python3.withPackages (pythonPackages: [ - pythonPackages.httpx - pythonPackages.pip - pythonPackages.pytest + (python3.withPackages (p: [ + p.httpx + p.pip + p.pytest ])) ]; }; diff --git a/package.nix b/package.nix new file mode 100644 index 0000000..d3e85ec --- /dev/null +++ b/package.nix @@ -0,0 +1,34 @@ +# Package the tartarus harness from uv.lock via uv2nix. +{ + pkgs, + lib, + pyproject-nix, + uv2nix, + pyproject-build-systems, +}: + +let + workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; }; + + python = + let + interpreters = pyproject-nix.lib.util.filterPythonInterpreters { + inherit (workspace) requires-python; + inherit (pkgs) pythonInterpreters; + }; + in + if interpreters == [ ] then + throw "tartarus: no Python interpreter matches requires-python = \"${workspace.requires-python}\"" + else + builtins.elemAt interpreters 0; + + pythonSet = (pkgs.callPackage pyproject-nix.build.packages { inherit python; }).overrideScope ( + lib.composeManyExtensions [ + pyproject-build-systems.overlays.wheel + (workspace.mkPyprojectOverlay { sourcePreference = "wheel"; }) + ] + ); +in +(pythonSet.mkVirtualEnv "tartarus-env" workspace.deps.default).overrideAttrs (_: { + meta.mainProgram = "tartarus"; +}) diff --git a/pyproject.toml b/pyproject.toml index 130a641..9538764 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,20 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + [project] name = "tartarus-nix" version = "0.1.0" -description = "A Nix-defined containment runtime for auditable agents." +description = "A framework for building composable, hermetic, and shareable AI agents with Nix." readme = "README.md" requires-python = ">=3.13" dependencies = [ "httpx>=0.28.1", ] +[project.scripts] +tartarus = "tartarus.cli:main" + [dependency-groups] dev = [ "pytest>=9.1.1", @@ -18,3 +25,6 @@ dev = [ [tool.pytest.ini_options] pythonpath = ["."] testpaths = ["tests"] + +[tool.hatch.build.targets.wheel] +packages = ["tartarus"] diff --git a/uv.lock b/uv.lock index 4cda813..76d9758 100644 --- a/uv.lock +++ b/uv.lock @@ -2,31 +2,6 @@ version = 1 revision = 3 requires-python = ">=3.13" -[[package]] -name = "tartarus-nix" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "httpx" }, -] - -[package.dev-dependencies] -dev = [ - { name = "pytest" }, - { name = "ruff" }, - { name = "ty" }, -] - -[package.metadata] -requires-dist = [{ name = "httpx", specifier = ">=0.28.1" }] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=9.1.1" }, - { name = "ruff", specifier = ">=0.15.20" }, - { name = "ty", specifier = ">=0.0.55" }, -] - [[package]] name = "anyio" version = "4.14.1" @@ -180,6 +155,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" }, ] +[[package]] +name = "tartarus-nix" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "httpx" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "ruff" }, + { name = "ty" }, +] + +[package.metadata] +requires-dist = [{ name = "httpx", specifier = ">=0.28.1" }] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=9.1.1" }, + { name = "ruff", specifier = ">=0.15.20" }, + { name = "ty", specifier = ">=0.0.55" }, +] + [[package]] name = "ty" version = "0.0.55"