A declarative, hermetic harness for Nix-defined agents.
0

Configure Feed

Select the types of activity you want to include in your feed.

README.md: nixify + add simple guides

Aly Raffauf (Jun 28, 2026, 12:16 PM EDT) 627a2118 b988d318

+71 -64
+71 -64
README.md
··· 18 18 Prerequisites: 19 19 20 20 - Nix with flakes enabled 21 - - Python 3.13+ and `uv` 22 21 - Linux with `bubblewrap` available for jailed execution (`x86_64-linux` or `aarch64-linux`) 23 22 - An OpenAI-compatible chat-completions endpoint 24 23 ··· 26 25 27 26 ```sh 28 27 export OPENCODE_API_KEY=... 29 - uv run python main.py "summarize the uncommitted changes in this repo" 28 + nix run .#tartarus -- "summarize the uncommitted changes in this repo" 30 29 ``` 31 30 32 31 With no prompt argument, Tartarus starts an interactive REPL: 33 32 34 33 ```sh 35 - uv run python main.py 34 + nix run .#tartarus 36 35 ``` 37 36 38 37 Assistant text, tool starts/finishes, and foreground command output stream live. ··· 51 50 52 51 That writes a minimal `flake.nix` and `agent.nix` (the `coding` module plus a 53 52 model block). The template's dev shell ships the packaged harness as the 54 - `tartarus` command, so you do not need `uv` or a checkout of this repo: 53 + `tartarus` command, so you do not need a checkout of this repo: 55 54 56 55 ```sh 57 56 nix develop ··· 85 84 86 85 ## Running Agents 87 86 88 - By default Tartarus builds and loads: 87 + Tartarus resolves the agent bundle at startup and caches nothing between runs. 88 + The algorithm is: 89 89 90 - ```text 91 - path:.#agents.<host-system>.default.config.build.bundle 92 - ``` 90 + 1. If `TARTARUS_BUNDLE` is set, use that store path directly and skip Nix. 91 + 2. Otherwise build `<TARTARUS_FLAKE_REF>#agents.<host-system>.<agent-name>.config.build.bundle` 92 + via `nix build --no-link --print-out-paths`. 93 93 94 - Select another agent from the same flake with either an env var or an inline 95 - selector: 94 + The three slots are: 95 + 96 + - `TARTARUS_FLAKE_REF` — the flake reference. Defaults to `path:.` (the current 97 + directory). Override with `github:org/repo`, `path:/some/dir`, etc. 98 + - `<host-system>` — derived from the host (the harness calls `nix build` for 99 + `x86_64-linux` or `aarch64-linux`). It is not configurable. 100 + - `<agent-name>` — precedence: an inline `.#<name>` selector as the first 101 + positional argument wins over `TARTARUS_AGENT`, which wins over `default`. 102 + 103 + The inline selector is a harness CLI convention (parsed after `nix run .#tartarus --`), 104 + not a flake output selector. Use `--` to protect it and the prompt from nix's 105 + argument parser: 96 106 97 107 ```sh 108 + # Env selector 98 109 export TARTARUS_AGENT=research 99 - uv run python main.py "inspect this project" 110 + nix run .#tartarus -- "inspect this project" 100 111 101 - uv run python main.py .#default "what packages are available on PyPI for typer?" 112 + # Inline selector (wins over the env var) 113 + nix run .#tartarus -- .#default "what packages are available on PyPI for typer?" 102 114 ``` 103 115 104 116 Point Tartarus at another flake: ··· 106 118 ```sh 107 119 export TARTARUS_FLAKE_REF=github:your-org/your-agents 108 120 export TARTARUS_AGENT=default 109 - uv run python main.py 121 + nix run .#tartarus 122 + ``` 123 + 124 + If multiple agents live under `agents.<system>`, name them in the flake and 125 + pick one with `TARTARUS_AGENT` or `.#<name>`: 126 + 127 + ```nix 128 + agents.${system} = { 129 + default = tartarus.lib.tartarusAgent { /* ... */ }; 130 + research = tartarus.lib.tartarusAgent { /* ... */ }; 131 + }; 110 132 ``` 111 133 112 134 Use a prebuilt/copied bundle without needing the source flake at runtime: 113 135 114 136 ```sh 137 + # On the build machine: 115 138 nix build .#agents.x86_64-linux.default.config.build.bundle --no-link --print-out-paths 116 139 nix copy --to <store-or-cache> /nix/store/...-bundle 117 140 118 141 # On the receiving machine: 119 142 nix copy --from <store-or-cache> /nix/store/...-bundle 120 - export TARTARUS_BUNDLE=/nix/store/...-bundle 121 - uv run python main.py 143 + TARTARUS_BUNDLE=/nix/store/...-bundle nix run github:alyraffauf/tartarus#tartarus 122 144 ``` 123 145 124 146 Secrets are never part of the bundle. API keys and deployment-specific headers ··· 132 154 133 155 ```nix 134 156 { 135 - inputs.tartarus.url = "github:your-org/tartarus"; 157 + inputs.tartarus.url = "github:alyraffauf/tartarus"; 136 158 inputs.nixpkgs.follows = "tartarus/nixpkgs"; 137 159 138 160 outputs = { self, tartarus, nixpkgs, ... }: 139 - let 140 - system = "x86_64-linux"; 141 - in 142 - { 161 + let system = "x86_64-linux"; in { 143 162 agents.${system}.default = tartarus.lib.tartarusAgent { 144 163 inherit system; 145 164 modules = [ 146 165 tartarus.modules.coding 147 166 ({ pkgs, ... }: { 167 + name = "default"; 148 168 systemPrompt = "You are a careful coding agent."; 149 - shell.packages = with pkgs; [ bash coreutils ]; 169 + 170 + model = { 171 + baseUrl = "https://opencode.ai/zen/v1"; 172 + name = "glm-5.2"; 173 + maxTokens = 32768; 174 + sampling = { temperature = 0.6; }; 175 + }; 150 176 151 177 capabilities.read_package_json = { 152 178 description = "Read package.json from the work tree."; 153 179 policy = "auto"; 154 - params = { }; 155 180 grants.packages = [ pkgs.jq ]; 156 181 runner = "jq . package.json"; 157 - }; 158 - 159 - model = { 160 - provider = "openai-compat"; 161 - baseUrl = "https://opencode.ai/zen/v1"; 162 - name = "glm-5.2"; 163 - maxTokens = 32768; 164 - sampling = { temperature = 0.6; }; 165 182 }; 166 183 }) 167 184 ]; 168 185 }; 169 - 170 - packages.${system}.default = self.agents.${system}.default.config.build.bundle; 171 186 }; 172 187 } 173 188 ``` ··· 184 199 capabilities 185 200 186 201 The baseline `shell` is shared by every jailed call, so keep it small. Put 187 - tool-specific programs in that capability's package grants. 202 + tool-specific programs in that capability's package grants and avoid duplicating 203 + packages that are already defaults (the base shell includes `bash` and `coreutils`). 188 204 189 - `tartarusAgent` mirrors `nixpkgs.lib.nixosSystem`: it takes 190 - `{ system, modules, specialArgs }` and configures its package set through a 191 - NixOS-style `nixpkgs` module. `nixpkgs.hostPlatform` defaults to `system`, and 192 - any module may set `nixpkgs.config` (e.g. `allowUnfree`), `nixpkgs.overlays`, or 193 - `nixpkgs.pkgs` to override it — every module then receives the result as `pkgs`. 205 + `tartarusAgent` takes `{ system, modules, specialArgs }`. `nixpkgs.hostPlatform` 206 + defaults to `system`; a module can override the package set with `nixpkgs.config`, 207 + `nixpkgs.overlays`, or `nixpkgs.pkgs`, and every module then receives the result 208 + as `pkgs`. Build outputs live at `config.build.{manifest,bundle,shell}`, hence 209 + `agents.<system>.<name>.config.build.bundle`. 194 210 195 - An agent's `name` option labels its bundle derivation (`tartarus-<name>-bundle`), 196 - mirroring how `networking.hostName` names a NixOS system. It defaults to `agent`; 197 - set it per agent (conventionally matching the `agents.<system>.<name>` key) for 198 - descriptive, non-colliding labels in multi-agent flakes. 211 + Set `name` per agent (conventionally matching the `agents.<system>.<name>` key) so 212 + its bundle derivation is labelled `tartarus-<name>-bundle` and multi-agent flakes do 213 + not collide. It defaults to `agent` otherwise. 199 214 200 - Like `nixosSystem`, `tartarusAgent` returns the module-evaluation result — 201 - `config`, `options`, `pkgs`, and `extendModules` — and its build outputs live in 202 - the config at `config.build.{manifest,bundle,shell}` (the agent analog of 203 - `config.system.build.toplevel`). Hence `agents.<system>.<name>.config.build.bundle`. 204 - 205 - `tartarus.modules` is a flat catalog of ordinary agent modules. Some entries set 206 - one capability (`read`, `list`, `write`, `edit`, `glob`, `grep`, `bash`, 207 - `webFetch`), while others can set any valid agent options. 208 - `tartarus.modules.coding` imports the common coding set, and 209 - `tartarus.modules.default` aliases it. 210 - Task/subagent orchestration, todo state, human questions, and skill loading are 211 - intentionally not modeled as shell capabilities yet. 215 + `tartarus.modules.coding` (aliased by `tartarus.modules.default`) imports the common 216 + coding set: `read`, `list`, `glob`, `grep`, `write`, `edit`, `bash`, and `webFetch`. 217 + Single-capability modules are available if you want to assemble a narrower agent. 212 218 213 219 ## Configuration 214 220 ··· 244 250 export TARTARUS_API_KEY=not-used 245 251 export TARTARUS_BASE_URL=http://localhost:11434/v1 246 252 export TARTARUS_MODEL=llama3.1 247 - uv run python main.py 253 + nix run .#tartarus 248 254 ``` 249 255 250 256 ## Sessions And Audit Logs ··· 253 259 inspect sessions with: 254 260 255 261 ```sh 256 - uv run python main.py "remember the number 42" 257 - uv run python main.py --continue "what number?" 258 - uv run python main.py --resume 20260627-1430 "continue here" 259 - uv run python main.py --list-sessions 260 - uv run python main.py --no-session "one-off" 262 + nix run .#tartarus -- "remember the number 42" 263 + nix run .#tartarus -- --continue "what number?" 264 + nix run .#tartarus -- --resume 20260627-1430 "continue here" 265 + nix run .#tartarus -- --list-sessions 266 + nix run .#tartarus -- --no-session "one-off" 261 267 ``` 262 268 263 269 Every brokered tool call appends one JSONL audit record, including policy ··· 277 283 278 284 ## Development 279 285 280 - Run the test suite: 286 + The repo is developed from the `nix develop` shell, which supplies Python and 287 + pytest for hacking on the harness. The packaged `tartarus` binary is exposed by 288 + the `tartarus` flake output, not by this dev shell. 281 289 282 - ```sh 283 - uv run pytest 284 - ``` 290 + See `AGENTS.md` for the developer workflow: running tests, lint, typecheck, and 291 + the dev-shell Python commands.