···1818Prerequisites:
19192020- Nix with flakes enabled
2121-- Python 3.13+ and `uv`
2221- Linux with `bubblewrap` available for jailed execution (`x86_64-linux` or `aarch64-linux`)
2322- An OpenAI-compatible chat-completions endpoint
2423···26252726```sh
2827export OPENCODE_API_KEY=...
2929-uv run python main.py "summarize the uncommitted changes in this repo"
2828+nix run .#tartarus -- "summarize the uncommitted changes in this repo"
3029```
31303231With no prompt argument, Tartarus starts an interactive REPL:
33323433```sh
3535-uv run python main.py
3434+nix run .#tartarus
3635```
37363837Assistant text, tool starts/finishes, and foreground command output stream live.
···51505251That writes a minimal `flake.nix` and `agent.nix` (the `coding` module plus a
5352model block). The template's dev shell ships the packaged harness as the
5454-`tartarus` command, so you do not need `uv` or a checkout of this repo:
5353+`tartarus` command, so you do not need a checkout of this repo:
55545655```sh
5756nix develop
···85848685## Running Agents
87868888-By default Tartarus builds and loads:
8787+Tartarus resolves the agent bundle at startup and caches nothing between runs.
8888+The algorithm is:
89899090-```text
9191-path:.#agents.<host-system>.default.config.build.bundle
9292-```
9090+1. If `TARTARUS_BUNDLE` is set, use that store path directly and skip Nix.
9191+2. Otherwise build `<TARTARUS_FLAKE_REF>#agents.<host-system>.<agent-name>.config.build.bundle`
9292+ via `nix build --no-link --print-out-paths`.
93939494-Select another agent from the same flake with either an env var or an inline
9595-selector:
9494+The three slots are:
9595+9696+- `TARTARUS_FLAKE_REF` — the flake reference. Defaults to `path:.` (the current
9797+ directory). Override with `github:org/repo`, `path:/some/dir`, etc.
9898+- `<host-system>` — derived from the host (the harness calls `nix build` for
9999+ `x86_64-linux` or `aarch64-linux`). It is not configurable.
100100+- `<agent-name>` — precedence: an inline `.#<name>` selector as the first
101101+ positional argument wins over `TARTARUS_AGENT`, which wins over `default`.
102102+103103+The inline selector is a harness CLI convention (parsed after `nix run .#tartarus --`),
104104+not a flake output selector. Use `--` to protect it and the prompt from nix's
105105+argument parser:
9610697107```sh
108108+# Env selector
98109export TARTARUS_AGENT=research
9999-uv run python main.py "inspect this project"
110110+nix run .#tartarus -- "inspect this project"
100111101101-uv run python main.py .#default "what packages are available on PyPI for typer?"
112112+# Inline selector (wins over the env var)
113113+nix run .#tartarus -- .#default "what packages are available on PyPI for typer?"
102114```
103115104116Point Tartarus at another flake:
···106118```sh
107119export TARTARUS_FLAKE_REF=github:your-org/your-agents
108120export TARTARUS_AGENT=default
109109-uv run python main.py
121121+nix run .#tartarus
122122+```
123123+124124+If multiple agents live under `agents.<system>`, name them in the flake and
125125+pick one with `TARTARUS_AGENT` or `.#<name>`:
126126+127127+```nix
128128+agents.${system} = {
129129+ default = tartarus.lib.tartarusAgent { /* ... */ };
130130+ research = tartarus.lib.tartarusAgent { /* ... */ };
131131+};
110132```
111133112134Use a prebuilt/copied bundle without needing the source flake at runtime:
113135114136```sh
137137+# On the build machine:
115138nix build .#agents.x86_64-linux.default.config.build.bundle --no-link --print-out-paths
116139nix copy --to <store-or-cache> /nix/store/...-bundle
117140118141# On the receiving machine:
119142nix copy --from <store-or-cache> /nix/store/...-bundle
120120-export TARTARUS_BUNDLE=/nix/store/...-bundle
121121-uv run python main.py
143143+TARTARUS_BUNDLE=/nix/store/...-bundle nix run github:alyraffauf/tartarus#tartarus
122144```
123145124146Secrets are never part of the bundle. API keys and deployment-specific headers
···132154133155```nix
134156{
135135- inputs.tartarus.url = "github:your-org/tartarus";
157157+ inputs.tartarus.url = "github:alyraffauf/tartarus";
136158 inputs.nixpkgs.follows = "tartarus/nixpkgs";
137159138160 outputs = { self, tartarus, nixpkgs, ... }:
139139- let
140140- system = "x86_64-linux";
141141- in
142142- {
161161+ let system = "x86_64-linux"; in {
143162 agents.${system}.default = tartarus.lib.tartarusAgent {
144163 inherit system;
145164 modules = [
146165 tartarus.modules.coding
147166 ({ pkgs, ... }: {
167167+ name = "default";
148168 systemPrompt = "You are a careful coding agent.";
149149- shell.packages = with pkgs; [ bash coreutils ];
169169+170170+ model = {
171171+ baseUrl = "https://opencode.ai/zen/v1";
172172+ name = "glm-5.2";
173173+ maxTokens = 32768;
174174+ sampling = { temperature = 0.6; };
175175+ };
150176151177 capabilities.read_package_json = {
152178 description = "Read package.json from the work tree.";
153179 policy = "auto";
154154- params = { };
155180 grants.packages = [ pkgs.jq ];
156181 runner = "jq . package.json";
157157- };
158158-159159- model = {
160160- provider = "openai-compat";
161161- baseUrl = "https://opencode.ai/zen/v1";
162162- name = "glm-5.2";
163163- maxTokens = 32768;
164164- sampling = { temperature = 0.6; };
165182 };
166183 })
167184 ];
168185 };
169169-170170- packages.${system}.default = self.agents.${system}.default.config.build.bundle;
171186 };
172187}
173188```
···184199 capabilities
185200186201The baseline `shell` is shared by every jailed call, so keep it small. Put
187187-tool-specific programs in that capability's package grants.
202202+tool-specific programs in that capability's package grants and avoid duplicating
203203+packages that are already defaults (the base shell includes `bash` and `coreutils`).
188204189189-`tartarusAgent` mirrors `nixpkgs.lib.nixosSystem`: it takes
190190-`{ system, modules, specialArgs }` and configures its package set through a
191191-NixOS-style `nixpkgs` module. `nixpkgs.hostPlatform` defaults to `system`, and
192192-any module may set `nixpkgs.config` (e.g. `allowUnfree`), `nixpkgs.overlays`, or
193193-`nixpkgs.pkgs` to override it — every module then receives the result as `pkgs`.
205205+`tartarusAgent` takes `{ system, modules, specialArgs }`. `nixpkgs.hostPlatform`
206206+defaults to `system`; a module can override the package set with `nixpkgs.config`,
207207+`nixpkgs.overlays`, or `nixpkgs.pkgs`, and every module then receives the result
208208+as `pkgs`. Build outputs live at `config.build.{manifest,bundle,shell}`, hence
209209+`agents.<system>.<name>.config.build.bundle`.
194210195195-An agent's `name` option labels its bundle derivation (`tartarus-<name>-bundle`),
196196-mirroring how `networking.hostName` names a NixOS system. It defaults to `agent`;
197197-set it per agent (conventionally matching the `agents.<system>.<name>` key) for
198198-descriptive, non-colliding labels in multi-agent flakes.
211211+Set `name` per agent (conventionally matching the `agents.<system>.<name>` key) so
212212+its bundle derivation is labelled `tartarus-<name>-bundle` and multi-agent flakes do
213213+not collide. It defaults to `agent` otherwise.
199214200200-Like `nixosSystem`, `tartarusAgent` returns the module-evaluation result —
201201-`config`, `options`, `pkgs`, and `extendModules` — and its build outputs live in
202202-the config at `config.build.{manifest,bundle,shell}` (the agent analog of
203203-`config.system.build.toplevel`). Hence `agents.<system>.<name>.config.build.bundle`.
204204-205205-`tartarus.modules` is a flat catalog of ordinary agent modules. Some entries set
206206-one capability (`read`, `list`, `write`, `edit`, `glob`, `grep`, `bash`,
207207-`webFetch`), while others can set any valid agent options.
208208-`tartarus.modules.coding` imports the common coding set, and
209209-`tartarus.modules.default` aliases it.
210210-Task/subagent orchestration, todo state, human questions, and skill loading are
211211-intentionally not modeled as shell capabilities yet.
215215+`tartarus.modules.coding` (aliased by `tartarus.modules.default`) imports the common
216216+coding set: `read`, `list`, `glob`, `grep`, `write`, `edit`, `bash`, and `webFetch`.
217217+Single-capability modules are available if you want to assemble a narrower agent.
212218213219## Configuration
214220···244250export TARTARUS_API_KEY=not-used
245251export TARTARUS_BASE_URL=http://localhost:11434/v1
246252export TARTARUS_MODEL=llama3.1
247247-uv run python main.py
253253+nix run .#tartarus
248254```
249255250256## Sessions And Audit Logs
···253259inspect sessions with:
254260255261```sh
256256-uv run python main.py "remember the number 42"
257257-uv run python main.py --continue "what number?"
258258-uv run python main.py --resume 20260627-1430 "continue here"
259259-uv run python main.py --list-sessions
260260-uv run python main.py --no-session "one-off"
262262+nix run .#tartarus -- "remember the number 42"
263263+nix run .#tartarus -- --continue "what number?"
264264+nix run .#tartarus -- --resume 20260627-1430 "continue here"
265265+nix run .#tartarus -- --list-sessions
266266+nix run .#tartarus -- --no-session "one-off"
261267```
262268263269Every brokered tool call appends one JSONL audit record, including policy
···277283278284## Development
279285280280-Run the test suite:
286286+The repo is developed from the `nix develop` shell, which supplies Python and
287287+pytest for hacking on the harness. The packaged `tartarus` binary is exposed by
288288+the `tartarus` flake output, not by this dev shell.
281289282282-```sh
283283-uv run pytest
284284-```
290290+See `AGENTS.md` for the developer workflow: running tests, lint, typecheck, and
291291+the dev-shell Python commands.