tare-cli 0.3.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. {tare_cli-0.3.0 → tare_cli-0.4.0}/CONCEPT.md +62 -48
  2. tare_cli-0.4.0/PKG-INFO +180 -0
  3. tare_cli-0.4.0/README.md +155 -0
  4. {tare_cli-0.3.0 → tare_cli-0.4.0}/pyproject.toml +1 -1
  5. {tare_cli-0.3.0 → tare_cli-0.4.0}/src/tare/agents.py +3 -3
  6. tare_cli-0.4.0/src/tare/answer.py +412 -0
  7. tare_cli-0.4.0/src/tare/assets/fonts/IBMPlexMono-Regular.woff2 +0 -0
  8. tare_cli-0.4.0/src/tare/assets/fonts/IBMPlexMono-SemiBold.woff2 +0 -0
  9. tare_cli-0.4.0/src/tare/assets/fonts/IBMPlexSans-Regular.woff2 +0 -0
  10. tare_cli-0.4.0/src/tare/assets/fonts/IBMPlexSans-SemiBold.woff2 +0 -0
  11. tare_cli-0.4.0/src/tare/assets/fonts/OFL.txt +93 -0
  12. tare_cli-0.4.0/src/tare/assets/logo-dark.svg +260 -0
  13. tare_cli-0.4.0/src/tare/assets/logo-light.svg +260 -0
  14. tare_cli-0.4.0/src/tare/assets/tare.css +228 -0
  15. {tare_cli-0.3.0 → tare_cli-0.4.0}/src/tare/calibrate.py +1 -1
  16. {tare_cli-0.3.0 → tare_cli-0.4.0}/src/tare/cli.py +23 -14
  17. {tare_cli-0.3.0 → tare_cli-0.4.0}/src/tare/cliff.py +11 -11
  18. tare_cli-0.4.0/src/tare/dashboard.html +359 -0
  19. {tare_cli-0.3.0 → tare_cli-0.4.0}/src/tare/dashboard.py +16 -2
  20. tare_cli-0.4.0/src/tare/overview.html +65 -0
  21. {tare_cli-0.3.0 → tare_cli-0.4.0}/src/tare/probe.py +11 -11
  22. {tare_cli-0.3.0 → tare_cli-0.4.0}/src/tare/room.py +23 -4
  23. {tare_cli-0.3.0 → tare_cli-0.4.0}/src/tare/swap.py +21 -21
  24. {tare_cli-0.3.0 → tare_cli-0.4.0}/src/tare/trail.py +6 -1
  25. tare_cli-0.4.0/tests/test_answer.py +117 -0
  26. tare_cli-0.4.0/tests/test_cli.py +56 -0
  27. {tare_cli-0.3.0 → tare_cli-0.4.0}/tests/test_cliff.py +7 -7
  28. {tare_cli-0.3.0 → tare_cli-0.4.0}/tests/test_dashboard.py +30 -3
  29. {tare_cli-0.3.0 → tare_cli-0.4.0}/tests/test_probe.py +2 -2
  30. {tare_cli-0.3.0 → tare_cli-0.4.0}/tests/test_room.py +29 -0
  31. {tare_cli-0.3.0 → tare_cli-0.4.0}/tests/test_swap.py +35 -7
  32. tare_cli-0.3.0/PKG-INFO +0 -506
  33. tare_cli-0.3.0/README.md +0 -481
  34. tare_cli-0.3.0/src/tare/dashboard.html +0 -269
  35. tare_cli-0.3.0/src/tare/overview.html +0 -78
  36. {tare_cli-0.3.0 → tare_cli-0.4.0}/.gitignore +0 -0
  37. {tare_cli-0.3.0 → tare_cli-0.4.0}/LICENSE +0 -0
  38. {tare_cli-0.3.0 → tare_cli-0.4.0}/src/tare/__init__.py +0 -0
  39. {tare_cli-0.3.0 → tare_cli-0.4.0}/src/tare/capsule.py +0 -0
  40. {tare_cli-0.3.0 → tare_cli-0.4.0}/src/tare/fake.py +0 -0
  41. {tare_cli-0.3.0 → tare_cli-0.4.0}/src/tare/journal.py +0 -0
  42. {tare_cli-0.3.0 → tare_cli-0.4.0}/src/tare/judge.py +0 -0
  43. {tare_cli-0.3.0 → tare_cli-0.4.0}/src/tare/recipe.py +0 -0
  44. {tare_cli-0.3.0 → tare_cli-0.4.0}/src/tare/usage.py +0 -0
  45. {tare_cli-0.3.0 → tare_cli-0.4.0}/tests/conftest.py +0 -0
  46. {tare_cli-0.3.0 → tare_cli-0.4.0}/tests/test_agents.py +0 -0
  47. {tare_cli-0.3.0 → tare_cli-0.4.0}/tests/test_agy.py +0 -0
  48. {tare_cli-0.3.0 → tare_cli-0.4.0}/tests/test_calibrate.py +0 -0
  49. {tare_cli-0.3.0 → tare_cli-0.4.0}/tests/test_fake.py +0 -0
  50. {tare_cli-0.3.0 → tare_cli-0.4.0}/tests/test_judge.py +0 -0
  51. {tare_cli-0.3.0 → tare_cli-0.4.0}/tests/test_pi.py +0 -0
  52. {tare_cli-0.3.0 → tare_cli-0.4.0}/tests/test_seatbelt.py +0 -0
  53. {tare_cli-0.3.0 → tare_cli-0.4.0}/tests/test_usage.py +0 -0
@@ -6,10 +6,13 @@ that it could not see your setup (`tare: not proven (blind)`). It refuses to sta
6
6
  until the reading is `tare: 0.00`, unless you pass `--allow-dirty`. It supports Claude Code, Codex, Pi
7
7
  and the Antigravity CLI (`agy`) on Linux and WSL, and Claude Code, Codex and Pi on macOS.
8
8
 
9
- This document describes the design and the measurements behind it. The [README](README.md)
10
- explains the words this document uses: room, probe, reading, leak, declared, dirty twin, check, tail and
11
- handoff. Every claim marked **measured** was observed on WSL2. The section
12
- [Measurements](#measurements) lists the agent versions and links each result.
9
+ This document describes the design and the measurements behind it. The documentation explains the
10
+ words this document uses: room, probe, reading, leak, declared and control run in
11
+ [docs/words.md](docs/words.md), check, restart and handover note in [docs/measuring.md](docs/measuring.md).
12
+ The experiment records under `experiments/` call the control run the control run and a restart a
13
+ tail.
14
+ Every claim marked **measured** was observed on WSL2. The section [Measurements](#measurements)
15
+ lists the agent versions and links each result.
13
16
 
14
17
  ## Why
15
18
 
@@ -71,11 +74,11 @@ your instructions, memories, plugins, hooks and skills. That is not enough.
71
74
  server keeps the request, which is the context the agent CLI put together, and answers "ok".
72
75
  The idea is the blank run from analytical chemistry, which runs the whole procedure without a
73
76
  sample to see what the procedure itself brings in.
74
- - **Control:** the same agent run in your real setup, the dirty twin, must show your context. The
77
+ - **Control:** the same agent run in your real setup, the control run, must show your context. The
75
78
  risk was that features that come with the login (connected accounts, account skills) would
76
79
  not load against a fake server, so the probe would read clean while a real run is not.
77
80
  **Measured:** for Claude Code this risk did not occur. Against the fake model server, the
78
- dirty twin still loaded the connected accounts, account skills, plugin skills, hook output,
81
+ control run still loaded the connected accounts, account skills, plugin skills, hook output,
79
82
  global instructions, memories and the email. The room loaded none of them except the email.
80
83
  Pointing Claude Code at a custom base URL turns its tool search off and puts every tool schema
81
84
  into the request. So the probe and the runs set `ENABLE_TOOL_SEARCH=true` to see the context as
@@ -122,22 +125,30 @@ session. This has not been tested, so tare refuses to start when the copy would
122
125
  within the next hour. **Measured:** the Antigravity CLI's Google login keeps its refresh token
123
126
  when it renews, so a room may renew its own copy.
124
127
 
128
+ **Measured** on Ubuntu on WSL2 with Claude Code 2.1.310: Claude Code renews its token only in
129
+ the last minutes before it expires. A session started outside tare with 57 minutes left, and
130
+ another with 7 minutes left, kept the old token; one started with under 6 minutes left renewed
131
+ it, and the new token held for 8 hours. So for most of the refused hour no session outside tare
132
+ can renew the login, and the refusal names the expiry time instead of asking for one. A run
133
+ known to be shorter may ask for less with `TARE_MIN_LOGIN_MINUTES`, never below 10 minutes, so
134
+ the copy does not start inside the renewal window (#126).
135
+
125
136
  ### Probe and reading
126
137
 
127
138
  The probe has the three parts of layer 3: the agent in the room against the fake model server,
128
- the dirty twin, and the reach script. It sorts what it finds into **kinds of context**,
139
+ the control run, and the reach script. It sorts what it finds into **kinds of context**,
129
140
  each checked on its own: instructions, memories, home path, MCP servers (connected accounts count
130
141
  here), skills, plugins, extensions, instruction files above the project, the account email, and
131
142
  reach.
132
143
 
133
144
  - A kind of context counts only when you have it. tare learns that from your files, for example
134
145
  a global instructions file or a skill folder.
135
- - If you have a kind of context and the dirty twin does not show it, the probe could not see
136
- it. tare then runs the dirty twin once more after 10 seconds. If the second dirty twin still
146
+ - If you have a kind of context and the control run does not show it, the probe could not see
147
+ it. tare then runs the control run once more after 10 seconds. If the second control run still
137
148
  does not show that kind of context, the reading is `tare: not proven (blind)`. **Measured:**
138
- with six runs starting together, a dirty twin sent its request before its MCP servers were
149
+ with six runs starting together, a control run sent its request before its MCP servers were
139
150
  connected.
140
- - Every probe makes no paid model call, because both the room and the dirty twin talk to the
151
+ - Every probe makes no paid model call, because both the room and the control run talk to the
141
152
  fake model server.
142
153
 
143
154
  ### Agents
@@ -145,7 +156,7 @@ reach.
145
156
  Each agent needs its own way to the fake model server, its own login copy and its own markers:
146
157
  lines of your own files that the probe looks for in a request.
147
158
 
148
- | Agent | How the room is set up | Way to the fake model server | Kinds of context the dirty twin showed on the measuring machine |
159
+ | Agent | How the room is set up | Way to the fake model server | Kinds of context the control run showed on the measuring machine |
149
160
  |---|---|---|---|
150
161
  | Claude Code | `CLAUDE_CONFIG_DIR` and `HOME` in the room, a copy of `.credentials.json`, `--strict-mcp-config --setting-sources project,local` | `ANTHROPIC_BASE_URL` | instructions, home path, MCP servers, skills, plugins, email, reach |
151
162
  | Codex | `CODEX_HOME` and `HOME` in the room, a copy of `auth.json` only, `--disable remote_plugin` | `-c openai_base_url=<server>/v1` keeps the ChatGPT login | instructions, memories, home path, skills, plugins, custom agents, reach |
@@ -172,15 +183,15 @@ What each agent needed:
172
183
  Codex's default model. Pass `-m` when you compare runs.
173
184
  - **Pi:** a fresh agent directory is clean on its own and keeps the project's `AGENTS.md`, so the
174
185
  room uses no flags. `--no-context-files` would drop the project's `AGENTS.md`. tare finds Pi's
175
- extension tools by comparing the dirty twin's tools with a run that uses `--no-extensions`.
186
+ extension tools by comparing the control run's tools with a run that uses `--no-extensions`.
176
187
  With standard input left open, Pi waits for piped input and never answers, so tare closes it.
177
188
  - **Antigravity CLI:** the fake model server speaks Google's Cloud Code `v1internal` API and
178
189
  offers one model. It lists that model in the groups the CLI shows models in
179
190
  (`agentModelSorts`), because the CLI rejects a model that no group lists. The
180
- CLI writes into `~/.gemini` on every run, so its dirty twin runs with an overlay over
191
+ CLI writes into `~/.gemini` on every run, so its control run runs with an overlay over
181
192
  `~/.gemini`. A user namespace mounts the overlay, and every write goes to a temporary file
182
193
  system that disappears after the run. **Measured:** `~/.gemini` kept its fingerprint (paths,
183
- sizes, modification times) through every dirty twin. The CLI writes each skill's path into
194
+ sizes, modification times) through every control run. The CLI writes each skill's path into
184
195
  its skill list, so the probe compares skill lines without the path. Without that, a skill that
185
196
  leaked into the room would not match yours, because its path differs.
186
197
  - **Instruction files above the project:** Claude Code's one-shot mode, which the probe uses, did
@@ -190,7 +201,7 @@ What each agent needed:
190
201
  - **Long prompts:** Linux limits a single argument to about 128 KiB. tare passes the task to
191
202
  every agent as an argument, so a longer task fails. Pi could read it from a file with `@file`.
192
203
 
193
- ### Capsules, trail and handoff
204
+ ### Capsules, trail and handover note
194
205
 
195
206
  Calibrate, Swap and Cliff need a run they can stop and continue. During a recorded run, a hook
196
207
  archives the workspace after every tool call. Each archive is a **capsule**. The hook comes from
@@ -204,16 +215,16 @@ A run continues from a capsule in one of two ways:
204
215
  resumes a session file cut after the step, with `codex exec resume <id>`. Pi resumes with
205
216
  `--session`. **Measured:** a resumed Codex session kept its id and finished the remaining plan
206
217
  from step 1.
207
- - **A handoff.** The **trail** is a record of a run in the same format for every agent: the task,
208
- the messages, the tool calls and their results. tare writes a handoff from any prefix of it, so
218
+ - **A handover note.** The **trail** is a record of a run in the same format for every agent: the task,
219
+ the messages, the tool calls and their results. tare writes a handover note from any prefix of it, so
209
220
  any agent can continue any run without reading another agent's session format. The Antigravity
210
221
  CLI stores its sessions in a format that tare cannot cut, so it always continues with a
211
- handoff. Its trail comes from `transcript_full.jsonl`.
222
+ handover note. Its trail comes from `transcript_full.jsonl`.
212
223
 
213
224
  ### Calibrate
214
225
 
215
226
  `tare calibrate` starts each side (an agent with its arguments) several times from a fresh copy
216
- of the project and reports each side's pass rate with a 95% interval (Wilson). A side can have
227
+ of the project and reports each side's pass rate with a likely range (a 95% Wilson interval). A side can have
217
228
  its own prompt, so two skills that start with different commands compare in one call. Without a
218
229
  check, a run counts as finished when its agent exits with 0, and tare keeps its workspace for
219
230
  scoring elsewhere. **Measured:** in a field test that compared two ideation skills, 18 rooms all
@@ -234,31 +245,32 @@ judge found a real one-cent rounding bug and an overflow.
234
245
 
235
246
  ### Swap
236
247
 
237
- Swap cuts two runs at several points. At each cut, each agent continues each run's workspace
238
- by handoff. Swap reports a state effect (how much the state of the workspace matters), a model
239
- effect (which agent does better), and, where an agent can continue its own saved session, the
240
- handoff cost. At cut 0 both workspaces are the untouched project, so the state effect there must
241
- be zero. Swap checks this, and the check is called the null check. When the state effect and the
242
- model effect are within 0.1 of each other, Swap says they explain the failure about equally.
248
+ Swap stops two runs at several handover points. At each one, each agent continues each run's
249
+ workspace from a handover note. Swap reports a workspace effect (how much the state of the
250
+ workspace matters), an agent effect (which agent does better), and, where an agent can continue
251
+ its own saved session, the note cost. At the handover point 0 both workspaces are the untouched
252
+ project, so the workspace effect there must be zero. Swap checks this, and the check is called the
253
+ start check. When the workspace effect and the agent effect are within 0.1 of each other, Swap
254
+ says they explain the failure about equally.
243
255
 
244
- **Measured:** against scripted models with a known truth, the null check passed and the blame
245
- passed from the model to the workspace between cut 0 and cut 0.5, as the scripts were written
246
- to cause.
256
+ **Measured:** against scripted models with a known truth, the start check passed and the blame
257
+ passed from the agent to the workspace between the handovers at 0 and 0.5, as the scripts were
258
+ written to cause.
247
259
 
248
260
  ### Cliff
249
261
 
250
262
  Cliff restarts a failed run from its capsules and names the step after which it no longer
251
- succeeds. The baseline is the set of tails from the start. A step counts as good while its
252
- tails pass at least half as often as the baseline. Cliff halves the range between the last good
253
- and the first bad step, then adds tails to the two neighbours until their intervals separate or
254
- the budget is spent. It reports a model gap instead of a step only when the upper end of the
255
- baseline's 95% interval lies below 0.2.
263
+ succeeds. Fresh starts are the restarts from the beginning. A step counts as good while its
264
+ restarts pass at least half as often as a fresh start. Cliff halves the range between the last
265
+ good and the first bad step, then adds restarts to the two neighbours until their likely ranges
266
+ separate or the budget is spent. It reports that the task is too hard for the model instead of a
267
+ step only when the upper end of the fresh starts' likely range (a 95% interval) lies below 0.2.
256
268
 
257
269
  **Measured:** against a scripted model whose wrong step is known, Cliff found step 4 with
258
- separated intervals in 18 tails. Cliff stays experimental, because neither of two real test
270
+ separated likely ranges in 18 restarts. Cliff stays experimental, because neither of two real test
259
271
  rounds had a failure that sat in one step (see [Measurements](#measurements)). Swap already
260
- shows, at three cuts, how often an agent passes when it continues its own workspace. When a real
261
- Swap shows that rate dropping between two cuts, Cliff can narrow the drop down to one step. Only
272
+ shows, at three handover points, how often an agent passes when it continues its own workspace. When a real
273
+ Swap shows that rate dropping between two handover points, Cliff can narrow the drop down to one step. Only
262
274
  then does Cliff get new experiments.
263
275
 
264
276
  ### Tokens and cost
@@ -279,7 +291,9 @@ judge read 186.4k tokens in and $0.093 in total, the Codex part estimated from L
279
291
 
280
292
  Calibrate, Swap and Cliff write everything into a run directory: a journal (one JSON line per
281
293
  event), a `recipe.json`, a `usage.json` with the totals, and the live output of every agent. The dashboard reads only the run
282
- directory, so `tare watch` shows a finished run the same way as a live one. The recipe holds the
294
+ directory, so `tare watch` shows a finished run the same way as a live one. Each run page opens with
295
+ the answer: a sentence from fixed templates per verdict, worked out from the run directory the way
296
+ Calibrate, Swap and Cliff decide, so a run recorded by an older tare reads like a new one. The recipe holds the
283
297
  command, a fingerprint of the project and the agent versions. `tare rerun` repeats a run and
284
298
  names what has changed since.
285
299
 
@@ -290,19 +304,19 @@ macOS 27.0 (arm64) on 2026-10-05 and 2026-10-06.
290
304
 
291
305
  | What | Agent and version | Result | Source |
292
306
  |---|---|---|---|
293
- | Dirty twin and room, fake model server | Claude Code 2.1.289 | the dirty twin showed every kind of context, the room only the email | [results](experiments/dirty-twin/RESULTS.md) |
294
- | Dirty twin and room | Codex CLI 0.160.0 | the room read `tare: 0.00` | [results](experiments/dirty-twin-codex/RESULTS.md) |
295
- | Dirty twin and room | Pi 1.0.2 | the room read `tare: 0.00` without flags | [results](experiments/dirty-twin-pi/RESULTS.md) |
296
- | Dirty twin and room | Antigravity CLI 1.2.16 | the room read `tare: 0.00` without flags, and `~/.gemini` stayed unchanged | [results](experiments/dirty-twin-agy/RESULTS.md) |
307
+ | Control run and room, fake model server | Claude Code 2.1.289 | the control run showed every kind of context, the room only the email | [results](experiments/dirty-twin/RESULTS.md) |
308
+ | Control run and room | Codex CLI 0.160.0 | the room read `tare: 0.00` | [results](experiments/dirty-twin-codex/RESULTS.md) |
309
+ | Control run and room | Pi 1.0.2 | the room read `tare: 0.00` without flags | [results](experiments/dirty-twin-pi/RESULTS.md) |
310
+ | Control run and room | Antigravity CLI 1.2.16 | the room read `tare: 0.00` without flags, and `~/.gemini` stayed unchanged | [results](experiments/dirty-twin-agy/RESULTS.md) |
297
311
  | Rooms given copies of real settings on purpose | all four | Claude Code 4 leaks, Codex 6, Pi 5, Antigravity CLI 3, each named with its source | the results above |
298
- | Scripted Cliff and Swap | Claude Code against scripted models | Cliff found step 4. Swap passed the null check and found the blame passing from the model to the workspace between cut 0 and cut 0.5 | `experiments/cliff-scripted`, `experiments/swap-scripted` |
312
+ | Scripted Cliff and Swap | Claude Code against scripted models | Cliff found step 4. Swap passed the start check and found the blame passing from the agent to the workspace between the handovers at 0 and 0.5 | `experiments/cliff-scripted`, `experiments/swap-scripted` |
299
313
  | Real task 1: a data migration | Claude Code with Sonnet and Haiku, Codex | Deleting the source file after a wrong-encoding import was planned as the point of no return. Swap showed that the workspace could still be repaired. Haiku passed about 1 in 14 runs, Codex with gpt-6.1-sol 4 of 4 | [results](experiments/migration/RESULTS.md) |
300
314
  | Real task 2: a web page judged by Sonnet | Claude Code with Haiku, Codex with gpt-6.1-sol | Swap confirmed all four predictions of the plan written before the runs. Cliff called a failed run unlucky, and a later rescoring of the same page confirmed it | [results](experiments/html/RESULTS.md) |
301
315
  | Field test: two ideation skills compared | Claude Code 2.1.289 with Opus 5.5 | all 18 rooms read `tare: 0.00`, including a skill that started `claude -p` inside the room | not published |
302
316
  | macOS: what a Seatbelt room has to handle | Claude Code 2.1.289, Codex CLI 0.159.0, Pi 0.99.1 | Claude Code's login is in the Keychain. With only the home directory denied, `/var/folders`, `/private/tmp` and the pasteboard stayed readable. Agents' own sandboxes fail inside a room (`sandbox_apply: Operation not permitted`) | comments on epic #85 |
303
317
  | macOS: a Seatbelt room with the real login | same | each agent answered one prompt. The room and its login copy were removed afterwards, and the Keychain item was unchanged (digest before and after). `tare probe` read `tare: 0.00` for all three | comments on epic #85 |
304
- | macOS: dirty twin, Seatbelt room, and parts of the real setup copied into the room | same | every room read `tare: 0.00`. Each copied part that reached the request was named with its source, and nothing under the home directory, under `/var/folders`, in `/private/tmp` or on the pasteboard was reachable from inside. Two kinds of Codex context are invisible to the probe on any system (#109) | [results](experiments/dirty-twin-macos/RESULTS.md) |
305
- | macOS: scripted Cliff and Swap, the judge | Claude Code 2.1.290 against scripted models; Microsoft Edge for the judge's screenshot | Cliff found step 4. Swap passed the null check and found the blame passing from the model to the workspace between cut 0 and cut 0.5. `tare judge` rendered a page and scored it. With a project that turns Claude Code's sandbox on, every Bash call failed in a Seatbelt room until tare turned it off | comments on epic #85 |
318
+ | macOS: control run, Seatbelt room, and parts of the real setup copied into the room | same | every room read `tare: 0.00`. Each copied part that reached the request was named with its source, and nothing under the home directory, under `/var/folders`, in `/private/tmp` or on the pasteboard was reachable from inside. Two kinds of Codex context are invisible to the probe on any system (#109) | [results](experiments/dirty-twin-macos/RESULTS.md) |
319
+ | macOS: scripted Cliff and Swap, the judge | Claude Code 2.1.290 against scripted models; Microsoft Edge for the judge's screenshot | Cliff found step 4. Swap passed the start check and found the blame passing from the agent to the workspace between the handovers at 0 and 0.5. `tare judge` rendered a page and scored it. With a project that turns Claude Code's sandbox on, every Bash call failed in a Seatbelt room until tare turned it off | comments on epic #85 |
306
320
 
307
321
  ## Open questions and ideas
308
322
 
@@ -312,11 +326,11 @@ macOS 27.0 (arm64) on 2026-10-05 and 2026-10-06.
312
326
  a fresh room never did. Cached account data is the suspected cause. Not tested.
313
327
  - **Claude Code login renewal:** whether a renewal inside the room logs out the real session. Not
314
328
  tested, so tare avoids it.
315
- - **Codex handoffs:** Codex can run several commands in one tool call, which makes its handoffs
329
+ - **Codex handover notes:** Codex can run several commands in one tool call, which makes its handover notes
316
330
  long.
317
- - **Cost:** every tail is a full agent run, so a Cliff search or a Swap costs as much as its tails.
331
+ - **Cost:** every restart is a full agent run, so a Cliff search or a Swap costs as much as its restarts.
318
332
  - **macOS:** the room is built on Seatbelt, released in 0.3.0 (epic #85). Not done: the
319
- Antigravity CLI, whose dirty twin needs Linux's overlay, so tare stops with a message there.
333
+ Antigravity CLI, whose control run needs Linux's overlay, so tare stops with a message there.
320
334
  Not tested: whether a program started through launchd or XPC runs outside the room's profile.
321
335
  - **Other probes, not built:** weighing the first request's input tokens before and after adding
322
336
  junk to the real setup, and file tripwires that report which secret files a run opened.
@@ -0,0 +1,180 @@
1
+ Metadata-Version: 2.5
2
+ Name: tare-cli
3
+ Version: 0.4.0
4
+ Summary: Prove your coding agent starts without your personal setup before you measure it.
5
+ Project-URL: Homepage, https://github.com/a-ratz/tare
6
+ Project-URL: Repository, https://github.com/a-ratz/tare
7
+ Project-URL: Issues, https://github.com/a-ratz/tare/issues
8
+ Project-URL: Concept, https://github.com/a-ratz/tare/blob/main/CONCEPT.md
9
+ Author: Andre Ratzenberger
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agent-evaluation,ai-agents,claude-code,codex,coding-agents,evals,llm-evaluation,sandbox
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Testing
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+
26
+ <div align="center">
27
+ <picture>
28
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/a-ratz/tare/main/.github/logo-dark.svg">
29
+ <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/a-ratz/tare/main/.github/logo-light.svg">
30
+ <img alt="tare" src="https://raw.githubusercontent.com/a-ratz/tare/main/.github/logo-light.svg" width="260">
31
+ </picture>
32
+
33
+ <p><b>Zero the scale before you weigh.</b><br>
34
+ Prove your coding agent starts without your personal setup, then measure it.</p>
35
+ </div>
36
+
37
+ <div align="center">
38
+
39
+ [![License: MIT][license-shield]][license-url]
40
+ [![PyPI][version-shield]][version-url]
41
+ [![Python 3.11+][python-shield]][python-url]
42
+ [![Linux | WSL | macOS][platform-shield]][platform-url]
43
+ [![Claude Code | Codex | Pi | Antigravity][agent-shield]][agent-url]
44
+
45
+ </div>
46
+
47
+ <div align="center">
48
+ <a href="#quick-start">Quick start</a> &middot;
49
+ <a href="#features">Features</a> &middot;
50
+ <a href="https://github.com/a-ratz/tare/blob/main/docs/README.md">Documentation</a> &middot;
51
+ <a href="https://github.com/a-ratz/tare/blob/main/CONCEPT.md">Concept</a> &middot;
52
+ <a href="https://github.com/a-ratz/tare/issues/new?template=bug_report.md">Report Bug</a>
53
+ </div>
54
+
55
+ <br>
56
+
57
+ ---
58
+
59
+ Your coding agent knows you. Every run loads your global instructions, memories, skills, MCP
60
+ servers and connected accounts. At work that is the point. In an evaluation it is noise. Compare
61
+ runs with and without a skill on your own machine, and both runs carry your setup. You measure
62
+ your machine, not the skill.
63
+
64
+ **tare** starts the agent in a room that holds nothing of yours and proves the room is clean
65
+ before every run. It works with Claude Code, Codex, Pi and Google's Antigravity CLI on Linux and
66
+ WSL, and with the first three on macOS.
67
+
68
+ ```text
69
+ $ tare probe claude
70
+ tare probe · claude 2.1.289 · /home/me/tare
71
+ control your real setup shows: instructions, home path, mcp, skills, plugins, email, reach
72
+ leaks none
73
+ declared
74
+ email account email subscription login
75
+ credentials /home/tare/.claude-config/.credentials.json copy of the login, needed by the CLI
76
+ tare: 0.00
77
+ ```
78
+
79
+ `tare: 0.00` is the zeroed scale. Nothing of your setup reached the model except what the reading
80
+ declares. A reading with leaks names each one and the file it came from, and the agent does not
81
+ start.
82
+
83
+ ## Quick start
84
+
85
+ ```bash
86
+ uv tool install tare-cli # or: pipx install tare-cli
87
+ tare probe claude # prove the room is clean
88
+ tare claude -- -p "fix the tests" # run Claude Code in it
89
+ ```
90
+
91
+ On Linux and WSL, tare also needs bubblewrap (`sudo apt install bubblewrap`). The
92
+ [install guide](https://github.com/a-ratz/tare/blob/main/docs/install.md) has the details per
93
+ platform, and [Reading a probe](https://github.com/a-ratz/tare/blob/main/docs/probe.md) explains
94
+ every line of the output above.
95
+
96
+ To compare agents, skills or models, `tare calibrate`, `tare swap` and `tare cliff` start many runs
97
+ and open a live dashboard that leads with the answer:
98
+
99
+ <picture>
100
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/a-ratz/tare/main/docs/assets/dashboard-calibrate-dark.png">
101
+ <img alt="tare's dashboard for a Calibrate run: Claude Code scored higher, 68 against 15 out of 100, with the scores of every run" src="https://raw.githubusercontent.com/a-ratz/tare/main/docs/assets/dashboard-calibrate-light.png">
102
+ </picture>
103
+
104
+ ## Features
105
+
106
+ - **Measures what the model receives.** The probe records the request the agent sends to a fake
107
+ model server on your machine. It makes no paid calls and never asks the agent about itself,
108
+ because an agent asked about its own context can refuse or be wrong.
109
+ [How it works](https://github.com/a-ratz/tare/blob/main/docs/how-it-works.md)
110
+ - **Knows when it cannot see.** A control run starts the agent in your real setup. If the probe could
111
+ not see your setup there, the reading is `tare: not proven (blind)` instead of a `0.00` that
112
+ proves nothing. [Reading a probe](https://github.com/a-ratz/tare/blob/main/docs/probe.md)
113
+ - **Keeps your files out and your login working.** The room has no home directory of yours, no
114
+ credentials of your other tools and no secret environment variables. It gets a fresh copy of
115
+ your login, which tare deletes after the run.
116
+ [Running agents in rooms](https://github.com/a-ratz/tare/blob/main/docs/running-agents.md)
117
+ - **Compares skills, plugins and models.** `tare calibrate` starts each side several times from
118
+ scratch and reports pass rates with their intervals.
119
+ [Calibrate](https://github.com/a-ratz/tare/blob/main/docs/calibrate.md)
120
+ - **Scores what no test can decide.** `tare judge` lets a second agent score a page by your
121
+ rubric, without agent or model names, and `tare judge-noise` shows how far its scores vary.
122
+ [Judge](https://github.com/a-ratz/tare/blob/main/docs/judge.md)
123
+ - **Tells the workspace from the agent.** `tare swap` lets two agents continue each other's
124
+ unfinished work and reports whether the workspace or the agent decided the outcome.
125
+ [Swap](https://github.com/a-ratz/tare/blob/main/docs/swap.md)
126
+ - **Finds the step where a failed run was lost** (experimental). `tare cliff` restarts the run
127
+ from its saved steps and names the step after which it no longer passes.
128
+ [Cliff](https://github.com/a-ratz/tare/blob/main/docs/cliff.md)
129
+ - **Live dashboard, cost and reruns.** The dashboard leads with each run's answer and follows it
130
+ live, each report ends with tokens and cost, and `tare rerun` repeats a run from its recipe.
131
+ [Dashboard and rerun](https://github.com/a-ratz/tare/blob/main/docs/dashboard.md) ·
132
+ [Tokens and cost](https://github.com/a-ratz/tare/blob/main/docs/tokens-and-cost.md)
133
+
134
+ ## When to use
135
+
136
+ | Use tare when | Look elsewhere when |
137
+ |---|---|
138
+ | you compare agent runs with and without a skill, plugin or prompt | you need protection from a hostile agent, because the room has network access and a working login |
139
+ | you benchmark agents on your own machine | you use the Antigravity CLI on macOS (not supported there yet) |
140
+ | you want a fresh-machine run without a fresh machine | you log in with an API key only (untested) |
141
+ | an agent failed a task and you want to know which step lost it | you want a single run explained without running the agent again, because Cliff runs it many times |
142
+
143
+ ## Documentation
144
+
145
+ | | |
146
+ |---|---|
147
+ | **Getting started** | [Install](https://github.com/a-ratz/tare/blob/main/docs/install.md) · [The words tare uses](https://github.com/a-ratz/tare/blob/main/docs/words.md) · [Running agents in rooms](https://github.com/a-ratz/tare/blob/main/docs/running-agents.md) · [Reading a probe](https://github.com/a-ratz/tare/blob/main/docs/probe.md) |
148
+ | **Measuring** | [Overview](https://github.com/a-ratz/tare/blob/main/docs/measuring.md) · [Calibrate](https://github.com/a-ratz/tare/blob/main/docs/calibrate.md) · [Judge](https://github.com/a-ratz/tare/blob/main/docs/judge.md) · [Swap](https://github.com/a-ratz/tare/blob/main/docs/swap.md) · [Cliff](https://github.com/a-ratz/tare/blob/main/docs/cliff.md) · [Tokens and cost](https://github.com/a-ratz/tare/blob/main/docs/tokens-and-cost.md) · [Dashboard and rerun](https://github.com/a-ratz/tare/blob/main/docs/dashboard.md) |
149
+ | **Background** | [How it works](https://github.com/a-ratz/tare/blob/main/docs/how-it-works.md) · [Concept: the design and every measurement](https://github.com/a-ratz/tare/blob/main/CONCEPT.md) · [Experiments](https://github.com/a-ratz/tare/tree/main/experiments) |
150
+
151
+ ## Status
152
+
153
+ tare is on PyPI as `tare-cli`. Claude Code, Codex and Pi run on Linux, WSL and macOS; the
154
+ Antigravity CLI runs on Linux and WSL, and stops with a message on macOS for now. What comes next
155
+ is in the [open epics](https://github.com/a-ratz/tare/issues?q=is%3Aissue+is%3Aopen+label%3Atype%3Aepic).
156
+
157
+ ## Contributing
158
+
159
+ Every change starts as an issue. Larger work is an epic, split into features, and one epic
160
+ becomes one branch and one pull request. [AGENTS.md](https://github.com/a-ratz/tare/blob/main/AGENTS.md)
161
+ describes the process, the checks and the conventions. Run the tests with `uv run pytest`.
162
+
163
+ ## License
164
+
165
+ MIT. See [LICENSE](https://github.com/a-ratz/tare/blob/main/LICENSE).
166
+
167
+ ---
168
+
169
+ Crafted with [Readme Craft](https://github.com/motiful/readme-craft)
170
+
171
+ [license-shield]: https://img.shields.io/badge/license-MIT-blue.svg
172
+ [license-url]: https://github.com/a-ratz/tare/blob/main/LICENSE
173
+ [version-shield]: https://img.shields.io/pypi/v/tare-cli.svg
174
+ [version-url]: https://pypi.org/project/tare-cli/
175
+ [python-shield]: https://img.shields.io/badge/python-3.11%2B-3776AB.svg
176
+ [python-url]: https://www.python.org/
177
+ [platform-shield]: https://img.shields.io/badge/platform-Linux%20%7C%20WSL%20%7C%20macOS-555555.svg
178
+ [platform-url]: https://github.com/a-ratz/tare/blob/main/docs/install.md
179
+ [agent-shield]: https://img.shields.io/badge/agents-Claude%20Code%20%7C%20Codex%20%7C%20Pi%20%7C%20Antigravity-D97757.svg
180
+ [agent-url]: https://github.com/a-ratz/tare/blob/main/docs/install.md
@@ -0,0 +1,155 @@
1
+ <div align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/a-ratz/tare/main/.github/logo-dark.svg">
4
+ <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/a-ratz/tare/main/.github/logo-light.svg">
5
+ <img alt="tare" src="https://raw.githubusercontent.com/a-ratz/tare/main/.github/logo-light.svg" width="260">
6
+ </picture>
7
+
8
+ <p><b>Zero the scale before you weigh.</b><br>
9
+ Prove your coding agent starts without your personal setup, then measure it.</p>
10
+ </div>
11
+
12
+ <div align="center">
13
+
14
+ [![License: MIT][license-shield]][license-url]
15
+ [![PyPI][version-shield]][version-url]
16
+ [![Python 3.11+][python-shield]][python-url]
17
+ [![Linux | WSL | macOS][platform-shield]][platform-url]
18
+ [![Claude Code | Codex | Pi | Antigravity][agent-shield]][agent-url]
19
+
20
+ </div>
21
+
22
+ <div align="center">
23
+ <a href="#quick-start">Quick start</a> &middot;
24
+ <a href="#features">Features</a> &middot;
25
+ <a href="https://github.com/a-ratz/tare/blob/main/docs/README.md">Documentation</a> &middot;
26
+ <a href="https://github.com/a-ratz/tare/blob/main/CONCEPT.md">Concept</a> &middot;
27
+ <a href="https://github.com/a-ratz/tare/issues/new?template=bug_report.md">Report Bug</a>
28
+ </div>
29
+
30
+ <br>
31
+
32
+ ---
33
+
34
+ Your coding agent knows you. Every run loads your global instructions, memories, skills, MCP
35
+ servers and connected accounts. At work that is the point. In an evaluation it is noise. Compare
36
+ runs with and without a skill on your own machine, and both runs carry your setup. You measure
37
+ your machine, not the skill.
38
+
39
+ **tare** starts the agent in a room that holds nothing of yours and proves the room is clean
40
+ before every run. It works with Claude Code, Codex, Pi and Google's Antigravity CLI on Linux and
41
+ WSL, and with the first three on macOS.
42
+
43
+ ```text
44
+ $ tare probe claude
45
+ tare probe · claude 2.1.289 · /home/me/tare
46
+ control your real setup shows: instructions, home path, mcp, skills, plugins, email, reach
47
+ leaks none
48
+ declared
49
+ email account email subscription login
50
+ credentials /home/tare/.claude-config/.credentials.json copy of the login, needed by the CLI
51
+ tare: 0.00
52
+ ```
53
+
54
+ `tare: 0.00` is the zeroed scale. Nothing of your setup reached the model except what the reading
55
+ declares. A reading with leaks names each one and the file it came from, and the agent does not
56
+ start.
57
+
58
+ ## Quick start
59
+
60
+ ```bash
61
+ uv tool install tare-cli # or: pipx install tare-cli
62
+ tare probe claude # prove the room is clean
63
+ tare claude -- -p "fix the tests" # run Claude Code in it
64
+ ```
65
+
66
+ On Linux and WSL, tare also needs bubblewrap (`sudo apt install bubblewrap`). The
67
+ [install guide](https://github.com/a-ratz/tare/blob/main/docs/install.md) has the details per
68
+ platform, and [Reading a probe](https://github.com/a-ratz/tare/blob/main/docs/probe.md) explains
69
+ every line of the output above.
70
+
71
+ To compare agents, skills or models, `tare calibrate`, `tare swap` and `tare cliff` start many runs
72
+ and open a live dashboard that leads with the answer:
73
+
74
+ <picture>
75
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/a-ratz/tare/main/docs/assets/dashboard-calibrate-dark.png">
76
+ <img alt="tare's dashboard for a Calibrate run: Claude Code scored higher, 68 against 15 out of 100, with the scores of every run" src="https://raw.githubusercontent.com/a-ratz/tare/main/docs/assets/dashboard-calibrate-light.png">
77
+ </picture>
78
+
79
+ ## Features
80
+
81
+ - **Measures what the model receives.** The probe records the request the agent sends to a fake
82
+ model server on your machine. It makes no paid calls and never asks the agent about itself,
83
+ because an agent asked about its own context can refuse or be wrong.
84
+ [How it works](https://github.com/a-ratz/tare/blob/main/docs/how-it-works.md)
85
+ - **Knows when it cannot see.** A control run starts the agent in your real setup. If the probe could
86
+ not see your setup there, the reading is `tare: not proven (blind)` instead of a `0.00` that
87
+ proves nothing. [Reading a probe](https://github.com/a-ratz/tare/blob/main/docs/probe.md)
88
+ - **Keeps your files out and your login working.** The room has no home directory of yours, no
89
+ credentials of your other tools and no secret environment variables. It gets a fresh copy of
90
+ your login, which tare deletes after the run.
91
+ [Running agents in rooms](https://github.com/a-ratz/tare/blob/main/docs/running-agents.md)
92
+ - **Compares skills, plugins and models.** `tare calibrate` starts each side several times from
93
+ scratch and reports pass rates with their intervals.
94
+ [Calibrate](https://github.com/a-ratz/tare/blob/main/docs/calibrate.md)
95
+ - **Scores what no test can decide.** `tare judge` lets a second agent score a page by your
96
+ rubric, without agent or model names, and `tare judge-noise` shows how far its scores vary.
97
+ [Judge](https://github.com/a-ratz/tare/blob/main/docs/judge.md)
98
+ - **Tells the workspace from the agent.** `tare swap` lets two agents continue each other's
99
+ unfinished work and reports whether the workspace or the agent decided the outcome.
100
+ [Swap](https://github.com/a-ratz/tare/blob/main/docs/swap.md)
101
+ - **Finds the step where a failed run was lost** (experimental). `tare cliff` restarts the run
102
+ from its saved steps and names the step after which it no longer passes.
103
+ [Cliff](https://github.com/a-ratz/tare/blob/main/docs/cliff.md)
104
+ - **Live dashboard, cost and reruns.** The dashboard leads with each run's answer and follows it
105
+ live, each report ends with tokens and cost, and `tare rerun` repeats a run from its recipe.
106
+ [Dashboard and rerun](https://github.com/a-ratz/tare/blob/main/docs/dashboard.md) ·
107
+ [Tokens and cost](https://github.com/a-ratz/tare/blob/main/docs/tokens-and-cost.md)
108
+
109
+ ## When to use
110
+
111
+ | Use tare when | Look elsewhere when |
112
+ |---|---|
113
+ | you compare agent runs with and without a skill, plugin or prompt | you need protection from a hostile agent, because the room has network access and a working login |
114
+ | you benchmark agents on your own machine | you use the Antigravity CLI on macOS (not supported there yet) |
115
+ | you want a fresh-machine run without a fresh machine | you log in with an API key only (untested) |
116
+ | an agent failed a task and you want to know which step lost it | you want a single run explained without running the agent again, because Cliff runs it many times |
117
+
118
+ ## Documentation
119
+
120
+ | | |
121
+ |---|---|
122
+ | **Getting started** | [Install](https://github.com/a-ratz/tare/blob/main/docs/install.md) · [The words tare uses](https://github.com/a-ratz/tare/blob/main/docs/words.md) · [Running agents in rooms](https://github.com/a-ratz/tare/blob/main/docs/running-agents.md) · [Reading a probe](https://github.com/a-ratz/tare/blob/main/docs/probe.md) |
123
+ | **Measuring** | [Overview](https://github.com/a-ratz/tare/blob/main/docs/measuring.md) · [Calibrate](https://github.com/a-ratz/tare/blob/main/docs/calibrate.md) · [Judge](https://github.com/a-ratz/tare/blob/main/docs/judge.md) · [Swap](https://github.com/a-ratz/tare/blob/main/docs/swap.md) · [Cliff](https://github.com/a-ratz/tare/blob/main/docs/cliff.md) · [Tokens and cost](https://github.com/a-ratz/tare/blob/main/docs/tokens-and-cost.md) · [Dashboard and rerun](https://github.com/a-ratz/tare/blob/main/docs/dashboard.md) |
124
+ | **Background** | [How it works](https://github.com/a-ratz/tare/blob/main/docs/how-it-works.md) · [Concept: the design and every measurement](https://github.com/a-ratz/tare/blob/main/CONCEPT.md) · [Experiments](https://github.com/a-ratz/tare/tree/main/experiments) |
125
+
126
+ ## Status
127
+
128
+ tare is on PyPI as `tare-cli`. Claude Code, Codex and Pi run on Linux, WSL and macOS; the
129
+ Antigravity CLI runs on Linux and WSL, and stops with a message on macOS for now. What comes next
130
+ is in the [open epics](https://github.com/a-ratz/tare/issues?q=is%3Aissue+is%3Aopen+label%3Atype%3Aepic).
131
+
132
+ ## Contributing
133
+
134
+ Every change starts as an issue. Larger work is an epic, split into features, and one epic
135
+ becomes one branch and one pull request. [AGENTS.md](https://github.com/a-ratz/tare/blob/main/AGENTS.md)
136
+ describes the process, the checks and the conventions. Run the tests with `uv run pytest`.
137
+
138
+ ## License
139
+
140
+ MIT. See [LICENSE](https://github.com/a-ratz/tare/blob/main/LICENSE).
141
+
142
+ ---
143
+
144
+ Crafted with [Readme Craft](https://github.com/motiful/readme-craft)
145
+
146
+ [license-shield]: https://img.shields.io/badge/license-MIT-blue.svg
147
+ [license-url]: https://github.com/a-ratz/tare/blob/main/LICENSE
148
+ [version-shield]: https://img.shields.io/pypi/v/tare-cli.svg
149
+ [version-url]: https://pypi.org/project/tare-cli/
150
+ [python-shield]: https://img.shields.io/badge/python-3.11%2B-3776AB.svg
151
+ [python-url]: https://www.python.org/
152
+ [platform-shield]: https://img.shields.io/badge/platform-Linux%20%7C%20WSL%20%7C%20macOS-555555.svg
153
+ [platform-url]: https://github.com/a-ratz/tare/blob/main/docs/install.md
154
+ [agent-shield]: https://img.shields.io/badge/agents-Claude%20Code%20%7C%20Codex%20%7C%20Pi%20%7C%20Antigravity-D97757.svg
155
+ [agent-url]: https://github.com/a-ratz/tare/blob/main/docs/install.md
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tare-cli"
3
- version = "0.3.0"
3
+ version = "0.4.0"
4
4
  description = "Prove your coding agent starts without your personal setup before you measure it."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -526,7 +526,7 @@ class Pi:
526
526
 
527
527
  @contextmanager
528
528
  def twin(self, real: Real, fake_url: str):
529
- """The dirty twin runs on a copy of the user's agent directory; the real one is never written."""
529
+ """The control run runs on a copy of the user's agent directory; the real one is never written."""
530
530
  with tempfile.TemporaryDirectory(prefix="tare-pi-twin-") as copy:
531
531
  agent_dir = Path(copy) / "agent"
532
532
  shutil.copytree(real.config, agent_dir, symlinks=True, ignore=shutil.ignore_patterns("sessions"))
@@ -641,7 +641,7 @@ class Antigravity:
641
641
 
642
642
  def discover(self) -> Real:
643
643
  if sys.platform == "darwin":
644
- raise TareError("the Antigravity CLI is not supported on macOS yet: its dirty twin needs Linux's overlay "
644
+ raise TareError("the Antigravity CLI is not supported on macOS yet: its control run needs Linux's overlay "
645
645
  "over ~/.gemini (epic #85)")
646
646
  home = Path.home()
647
647
  return Real(home, home / ".gemini" / "antigravity-cli", _which("agy"))
@@ -679,7 +679,7 @@ class Antigravity:
679
679
 
680
680
  @contextmanager
681
681
  def twin(self, real: Real, fake_url: str):
682
- """The dirty twin sees the real setup, but agy writes into ~/.gemini on every run (its
682
+ """The control run sees the real setup, but agy writes into ~/.gemini on every run (its
683
683
  conversations, caches, onboarding state). So it runs with an overlay over ~/.gemini
684
684
  whose writes go to a tmpfs that dies with the run; see twin_argv."""
685
685
  mount = tempfile.mkdtemp(prefix="tare-agy-twin-")