@turri-dev/cli 0.1.10-next.2abf2db → 0.1.11-next.056c7c7

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.
package/README.md CHANGED
@@ -10,9 +10,11 @@
10
10
 
11
11
  **turri** is named after *Turritopsis dohrnii*, the immortal jellyfish that regenerates itself from its young form, again and again - as **turri** regenerates the code from the durable intent - see [the name](#the-name).
12
12
 
13
+ **turri** has a desktop app too: [turri desktop](https://github.com/turri-oss/turri-desktop).
14
+
13
15
  ## Quick start
14
16
 
15
- **turri** is built on [pi](https://pi.dev), the coding agent, and runs wherever pi runs: macOS, Linux, WSL; native Windows best effort, with Git Bash. You need git, bash and [Node](https://nodejs.org/en/download) 22.19 or newer - a Linux distribution's own `nodejs` package is often older. The agents use this machine's toolchains, fenced by a command check and an audit everywhere, and an OS sandbox where the OS has one - Seatbelt on macOS, bubblewrap on Linux (`apt install bubblewrap`) - see [installation](docs/installation.md#installation).
17
+ **turri** is built on [pi](https://pi.dev), the coding agent, and runs wherever [pi](https://pi.dev) runs: macOS, Linux, WSL; native Windows best effort, with Git Bash. You need git, bash and [Node](https://nodejs.org/en/download) 22.19 or newer - a Linux distribution's own `nodejs` package is often older. The agents use this machine's toolchains, fenced by a command check and an audit everywhere, and an OS sandbox where the OS has one - Seatbelt on macOS, bubblewrap on Linux (`apt install bubblewrap`) - see [installation](docs/installation.md#installation).
16
18
 
17
19
  ```bash
18
20
  npm install -g @turri-dev/cli # puts `turri` on your PATH
@@ -21,7 +23,7 @@ turri doctor # checks the machine
21
23
 
22
24
  To follow `main` as it moves, or work on turri itself, run it [from a clone](#from-a-clone) instead.
23
25
 
24
- `doctor` checks for Node 22.19+, bash, the OS sandbox and a model provider for pi; in a project, the toolchains its modules need, what its testing scripts run against this machine, and that each model `config.turri.yaml` names answers with your credentials. It changes nothing without asking: when a check fails - the OS sandbox does not start, say - it says why, and the command for you to run. Run it again whenever something breaks. What each check means: [installation](docs/installation.md).
26
+ `doctor` checks for Node 22.19+, bash, the OS sandbox and a model provider for [pi](https://pi.dev); in a project, the toolchains its modules need, what its testing scripts run against this machine, and that each model `config.turri.yaml` names answers with your credentials. It changes nothing without asking: when a check fails - the OS sandbox does not start, say - it says why, and the command for you to run. Run it again whenever something breaks. What each check means: [installation](docs/installation.md).
25
27
 
26
28
  Then start it in your project, signed in to your model providers:
27
29
 
@@ -99,7 +101,7 @@ With **turri** you work on the durable intent. You say what the software must do
99
101
 
100
102
  ## How it works
101
103
 
102
- **turri** captures the durable intent in [\*\*\*plain](https://plainlang.org/), a language for saying what software should do. \*\*\*plain breaks the intent down into functionalities - small units, each a single thing the software does - and **turri** regenerates the code from them piece by piece.
104
+ **turri** captures the durable intent in [\*\*\*plain](https://plainlang.org/docs/), a language for saying what software should do. \*\*\*plain breaks the intent down into functionalities - small units, each a single thing the software does - and **turri** regenerates the code from them piece by piece.
103
105
 
104
106
  1. **Define.** You say what the software should do; **turri** writes it down as \*\*\*plain.
105
107
  2. **Review and refine.** You review \*\*\*plain and change it with **turri** until it says what you mean.
@@ -125,7 +127,7 @@ So the tools speak its language: **`regenerate`** regenerates the code from \*\*
125
127
 
126
128
  ## Models
127
129
 
128
- `init-config`'s models are only a starting point. **turri** is built on [pi](https://github.com/earendil-works/pi), so any model pi supports works - Anthropic, OpenAI, Google, Amazon Bedrock, OpenRouter, a local one. A model is reached as pi reaches it - `/login`, in `turri` as in `pi`, the provider's API key in the environment (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, ...), or a cloud account - and named as pi names it, `provider/model-id`, with `:high`, `:low` and so on for how hard it thinks. It uses three: the **harness** model you talk to, which writes \*\*\*plain and plans the work - worth a capable one - and the **coding** and **judge** models its agents build and judge with, which can be smaller and cheaper, such as GPT-6 Luna or Gemini 3.1 Flash Lite. `turri --harness-model` and `--coding-model` choose them for a session, and pi's `/model` switches the one you talk to as you go; to keep them for the project, set them with `/turri-config harness-model`, `/turri-config coding-model` and so on - each role's too, as `models.<role>` - or write them in its `config.turri.yaml`:
130
+ `init-config`'s models are only a starting point. **turri** is built on [pi](https://pi.dev), so any model [pi](https://pi.dev) supports works - Anthropic, OpenAI, Google, Amazon Bedrock, OpenRouter, a local one. A model is reached as [pi](https://pi.dev) reaches it - `/login`, in `turri` as in `pi`, the provider's API key in the environment (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, ...), or a cloud account - and named as [pi](https://pi.dev) names it, `provider/model-id`, with `:high`, `:low` and so on for how hard it thinks. It uses three: the **harness** model you talk to, which writes \*\*\*plain and plans the work - worth a capable one - and the **coding** and **judge** models its agents build and judge with, which can be smaller and cheaper, such as GPT-6 Luna or Gemini 3.1 Flash Lite. `turri --harness-model` and `--coding-model` choose them for a session, and [pi](https://pi.dev)'s `/model` switches the one you talk to as you go; to keep them for the project, set them with `/turri-config harness-model`, `/turri-config coding-model` and so on - each role's too, as `models.<role>` - or write them in its `config.turri.yaml`:
129
131
 
130
132
  ```yaml
131
133
  harness-model: anthropic/claude-opus-5-5 # the model you talk to: a capable one
package/docs/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # turri documentation
2
2
 
3
- turri implements regenerative software: the \*\*\*plain specs are the durable definition of what a system does, and the code is generated from them, validated against them, and regenerated as they change. The [project README](../README.md) says why, and how to install and start; these pages go into each part.
3
+ turri implements regenerative software: [\*\*\*plain](https://plainlang.org/docs/) captures the durable definition of what a system does, and the code is regenerated from it and validated against. The [project README](../README.md) says why, and how to install and start; these pages go into each part.
4
4
 
5
5
  ## Start here
6
6
 
@@ -1,6 +1,6 @@
1
1
  # Authoring \*\*\*plain
2
2
 
3
- Rendering turns a spec into code; authoring is the half that decides what gets built. The code is only ever as good as the spec - a fact in the wrong section is silently ignored, a functional spec that is too big comes out half-built, two specs that contradict each other break each other's tests. So this repo carries as much for writing specs as for rendering them: the authoring rules, a library of skills that apply them, and independent review of what they write.
3
+ A spec is written in [\*\*\*plain](https://plainlang.org/docs/) - its documentation is the language's whole reference. Rendering turns a spec into code; authoring is the half that decides what gets built. The code is only ever as good as the spec - a fact in the wrong section is silently ignored, a functional spec that is too big comes out half-built, two specs that contradict each other break each other's tests. So this repo carries as much for writing specs as for rendering them: the authoring rules, a library of skills that apply them, and independent review of what they write.
4
4
 
5
5
  The rule underneath all of it: **the specs are the source of truth**. The generated code in `plain_modules/` is never edited by hand. A bug is fixed in the spec, and a change that is wanted goes into the spec - that is what persists and what the next render builds from.
6
6
 
package/docs/config.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # The project config
2
2
 
3
- A project says how its specs are built in `config.turri.yaml`, at the project root. Everything in it is optional: with no file at all, turri finds the testing scripts on its own, turns conformance tests on wherever a module has test reqs, and runs on pi's configured model. A project whose parts are on different stacks - a backend and a frontend - adds a file per stack, `config.turri.<stack>.yaml`, beside it, and chooses the stack to build with (see [stacks](#stacks)). turri never writes a `config.yaml`: it may be another tool's, and is read only by a project that has no file of turri's yet (see [the old config.yaml](#the-old-configyaml)).
3
+ A project says how its specs are built in `config.turri.yaml`, at the project root. Everything in it is optional: with no file at all, turri finds the testing scripts on its own, turns conformance tests on wherever a module has test reqs, and runs on [pi](https://pi.dev)'s configured model. A project whose parts are on different stacks - a backend and a frontend - adds a file per stack, `config.turri.<stack>.yaml`, beside it, and chooses the stack to build with (see [stacks](#stacks)). turri never writes a `config.yaml`: it may be another tool's, and is read only by a project that has no file of turri's yet (see [the old config.yaml](#the-old-configyaml)).
4
4
 
5
5
  `turri init-config` starts one: the two models, a strong one to talk to and a smaller one to write the code. It asks which vendor's models you use - `--vendor` says it without asking, and with neither, as from a script, it is `other`:
6
6
 
7
- | vendor | harness-model | coding-model | key pi reads |
7
+ | vendor | harness-model | coding-model | key [pi](https://pi.dev) reads |
8
8
  | --- | --- | --- | --- |
9
9
  | `anthropic` | `anthropic/claude-opus-5-5` | `anthropic/claude-haiku-5-5` | `ANTHROPIC_API_KEY` |
10
10
  | `openai` | `openai/gpt-6-astra` | `openai/gpt-6-luna` | `OPENAI_API_KEY` |
@@ -13,13 +13,13 @@ A project says how its specs are built in `config.turri.yaml`, at the project ro
13
13
  | `amazon-bedrock`, OpenAI | `amazon-bedrock/<region>.openai.gpt-6-astra` | `amazon-bedrock/<region>.openai.gpt-6-luna` | `AWS_PROFILE` or `AWS_BEARER_TOKEN_BEDROCK`, and `AWS_REGION` |
14
14
  | `other` | `anthropic/claude-opus-5-5` | `google/gemini-3.1-flash-lite` | `ANTHROPIC_API_KEY`, `GEMINI_API_KEY` |
15
15
 
16
- Amazon Bedrock asks three more things, from pi's catalogue: whose models (`--maker`) - any maker pi has there, Anthropic, OpenAI, xAI, Meta, Mistral, Qwen, DeepSeek, Amazon, Moonshot, Z.ai, ... - the geography of the inference profile they run in (`--region`) - `global`, `us`, `eu`, `jp`, `au`, `apac`, `in`, `ca` or `us-gov`, as the maker's models are served, or `in-region`, a model served in the AWS region it is called in - and which two. The pair offered is turri's own where it has one - the table's, for Anthropic's and OpenAI's in the geographies they are served in - and otherwise the catalogue's: the strongest model that reasons to talk to, by price, and the cheapest costing at least a fifth of it to code, ties to the newest. Enter takes it; `c` chooses each from the maker's models there. It says which `AWS_REGION` to set for the geography: pi calls Bedrock in `AWS_REGION`, else the profile's region, else `us-east-1` - so every geography but `global`, `us` and `in-region` needs it set (`eu-central-1` for `eu`, `ap-northeast-1` for `jp`, `ap-southeast-2` for `au`, `ap-southeast-1` for `apac`, `ap-south-1` for `in`, `ca-central-1` for `ca`, `us-gov-west-1` for `us-gov`). A `config.turri.yaml` already there keeps everything but its two models, which it sets; `--force` writes the whole file again.
16
+ Amazon Bedrock asks three more things, from [pi](https://pi.dev)'s catalogue: whose models (`--maker`) - any maker [pi](https://pi.dev) has there, Anthropic, OpenAI, xAI, Meta, Mistral, Qwen, DeepSeek, Amazon, Moonshot, Z.ai, ... - the geography of the inference profile they run in (`--region`) - `global`, `us`, `eu`, `jp`, `au`, `apac`, `in`, `ca` or `us-gov`, as the maker's models are served, or `in-region`, a model served in the AWS region it is called in - and which two. The pair offered is turri's own where it has one - the table's, for Anthropic's and OpenAI's in the geographies they are served in - and otherwise the catalogue's: the strongest model that reasons to talk to, by price, and the cheapest costing at least a fifth of it to code, ties to the newest. Enter takes it; `c` chooses each from the maker's models there. It says which `AWS_REGION` to set for the geography: [pi](https://pi.dev) calls Bedrock in `AWS_REGION`, else the profile's region, else `us-east-1` - so every geography but `global`, `us` and `in-region` needs it set (`eu-central-1` for `eu`, `ap-northeast-1` for `jp`, `ap-southeast-2` for `au`, `ap-southeast-1` for `apac`, `ap-south-1` for `in`, `ca-central-1` for `ca`, `us-gov-west-1` for `us-gov`). A `config.turri.yaml` already there keeps everything but its two models, which it sets; `--force` writes the whole file again.
17
17
 
18
- `--harness-model` and `--coding-model` name the two models instead, for any vendor: provider/model-id without a thinking level - `turri init-config --harness-model amazon-bedrock/global.xai.grok-4.7 --coding-model amazon-bedrock/global.xai.grok-4.6`. They are written wherever the vendor's own would be - the two models with their thinking, the commented-out `judge-model` and `models:` examples - and the vendor, maker and geography are read from them. `--judge-model` names the strong one the judging examples use, the harness model unless given. Each is checked against pi's catalogue first: one it does not have, or that is not a chat model, is named, with the closest it has, and nothing is written.
18
+ `--harness-model` and `--coding-model` name the two models instead, for any vendor: provider/model-id without a thinking level - `turri init-config --harness-model amazon-bedrock/global.xai.grok-4.7 --coding-model amazon-bedrock/global.xai.grok-4.6`. They are written wherever the vendor's own would be - the two models with their thinking, the commented-out `judge-model` and `models:` examples - and the vendor, maker and geography are read from them. `--judge-model` names the strong one the judging examples use, the harness model unless given. Each is checked against [pi](https://pi.dev)'s catalogue first: one it does not have, or that is not a chat model, is named, with the closest it has, and nothing is written.
19
19
 
20
20
  Each is written with how hard it thinks - `harness-model: openai/gpt-6-astra:medium`, `coding-model: openai/gpt-6-luna:low` - so the file shows it. The reviews and judges run on the coding model but think medium (see [models per role](#models-per-role)).
21
21
 
22
- They are an example, not a requirement - any model [pi](https://pi.dev) supports works, signed in any way pi supports; `other` is the starting point for another provider. What we recommend is that split, a capable model to talk to and a smaller, cheaper one for the coding. Once written, it says what to do next: the key to set in your shell - or `/login` in turri, which pi keeps in `~/.pi/agent/auth.json` - and that the file belongs to that folder: turri reads the nearest `config.turri.yaml` upwards from where it starts, so one written anywhere else is not seen.
22
+ They are an example, not a requirement - any model [pi](https://pi.dev) supports works, signed in any way [pi](https://pi.dev) supports; `other` is the starting point for another provider. What we recommend is that split, a capable model to talk to and a smaller, cheaper one for the coding. Once written, it says what to do next: the key to set in your shell - or `/login` in turri, which [pi](https://pi.dev) keeps in `~/.pi/agent/auth.json` - and that the file belongs to that folder: turri reads the nearest `config.turri.yaml` upwards from where it starts, so one written anywhere else is not seen.
23
23
 
24
24
  Below them it writes every other setting out, each with what it does and the value turri uses for it - `review: false`, `turn-limit: 120`, `parallel-jobs` as this machine's cores make it - so the file says how the project is built, and you change one by changing it. The optional ones, which have no value of their own - the testing scripts (written in by the skill that writes them), `module`, `conformance-budget`, `judge-model`, `models` - are there commented out, each with an example: uncomment one to use it. A key taken out of the file is still read as the table below says.
25
25
 
@@ -80,7 +80,7 @@ models: # each agent role's model, thinking and tur
80
80
  | key | what it sets | left out |
81
81
  | --- | --- | --- |
82
82
  | `module` | the `.plain` file `turri regenerate` and the chat's `regenerate` build when none is named, from the project root | a file has to be named |
83
- | `harness-model` | the model `turri` starts on - the one you talk to - and, in the conversation, the planner that cuts the work into jobs (on whatever model the chat is on when it plans); under `turri regenerate`, the planner too | pi's configured model |
83
+ | `harness-model` | the model `turri` starts on - the one you talk to - and, in the conversation, the planner that cuts the work into jobs (on whatever model the chat is on when it plans); under `turri regenerate`, the planner too | [pi](https://pi.dev)'s configured model |
84
84
  | `coding-model` | every render: functionalities, spec diffs, conformance tests, repairs, planning spec diffs, the explorer | `harness-model` |
85
85
  | `judge-model` | the judge and the test review of a conformance run | `coding-model` |
86
86
  | `conformance-tests` | `all` (or `true`): conformance tests for every functional spec of every module with test reqs, written once each module's code is built, before anything is built on it. `chosen`: for the functional specs chosen - every one with acceptance tests, and those the chooser picks as each code pulse ends, from what that pulse regenerated. `module`: the same, but the chooser is asked once, when the whole module is regenerated, and plans the conformance pulses too. Under both, the choice is made inside the pulse, before anything is built on the module, and the pulse's report says what was chosen and why; those the person asks for with `conform` are written too; once chosen, a functional spec keeps them, and they are rewritten when it changes - see [who chooses, and when](regenerating.md#conformance-tests-in-the-work-loop). `false`: none written, and none of the existing ones run - until the conversation asks with `conform` | `false` |
@@ -130,9 +130,9 @@ Keys are written with dashes; underscores are read the same (`conformance_tests`
130
130
 
131
131
  What an entry leaves out it takes from `default`: `reviewer: { thinking: low }` runs the default model, thinking little. A turn limit is the role's own, else `default`'s, else `turn-limit:`, else two minutes. A stack's `config.turri.<stack>.yaml` lays its own `models:` over the project's, role by role - all but `chat`, the project's alone.
132
132
 
133
- A role whose thinking nothing names - neither its entry, `default`'s, nor a `:level` on the model it runs on - thinks as its kind of work needs: **low** for the agents that do the work (`coding`, `conformance`, `explorer`, `release`), which work from specs that leave little to guess and take many turns, and **medium** for the ones that judge (`reviewer`, `test-reviewer`, `judge`, `arbiter`, `debugger`, `planner`, `chooser`, `diagnosis`), where a wrong call costs pulses. A judging role that falls back on the coding model takes its model alone, not its thinking. The conversation keeps its own level, as pi sets it.
133
+ A role whose thinking nothing names - neither its entry, `default`'s, nor a `:level` on the model it runs on - thinks as its kind of work needs: **low** for the agents that do the work (`coding`, `conformance`, `explorer`, `release`), which work from specs that leave little to guess and take many turns, and **medium** for the ones that judge (`reviewer`, `test-reviewer`, `judge`, `arbiter`, `debugger`, `planner`, `chooser`, `diagnosis`), where a wrong call costs pulses. A judging role that falls back on the coding model takes its model alone, not its thinking. The conversation keeps its own level, as [pi](https://pi.dev) sets it.
134
134
 
135
- The thinking level reaches the model as each provider takes it. OpenAI's models on Bedrock are sent it as their reasoning effort (`none`, `low`, `medium`, `high`, `xhigh`) by turri itself, as pi sends them none.
135
+ The thinking level reaches the model as each provider takes it. OpenAI's models on Bedrock are sent it as their reasoning effort (`none`, `low`, `medium`, `high`, `xhigh`) by turri itself, as [pi](https://pi.dev) sends them none.
136
136
 
137
137
  ## How many jobs at once
138
138
 
@@ -158,7 +158,7 @@ They are defaults. What is asked for wins over them:
158
158
 
159
159
  | model | wins over the config |
160
160
  | --- | --- |
161
- | harness | `turri --harness-model <m>` (or pi's own `--model`), and pi's `/model` in the conversation |
161
+ | harness | `turri --harness-model <m>` (or [pi](https://pi.dev)'s own `--model`), and [pi](https://pi.dev)'s `/model` in the conversation |
162
162
  | coding | `turri --coding-model <m>`, `engine` in the conversation |
163
163
  | judge | - |
164
164
 
package/docs/deploy.md CHANGED
@@ -45,6 +45,6 @@ Off by default: without the key, releasing after a pulse is the chat's, when its
45
45
  turri release [file.plain] [--in-module <module>] [--guidance <text>] [--model <provider/id>]
46
46
  ```
47
47
 
48
- The same, without the chat: a one-off agent on `coding-model`, thinking low (or `--model`; without either, pi's default) is given the project's AGENTS.md and what `--guidance <text>` says, looks for guidance in them, and releases as above - as the guidance says, or to `dist/<module>` - then works out how to start it and starts it here, installing what it needs without asking. It stays in the foreground with the servers' output; `Ctrl-C` stops them. If it does not get there, deploy from the chat instead.
48
+ The same, without the chat: a one-off agent on `coding-model`, thinking low (or `--model`; without either, [pi](https://pi.dev)'s default) is given the project's AGENTS.md and what `--guidance <text>` says, looks for guidance in them, and releases as above - as the guidance says, or to `dist/<module>` - then works out how to start it and starts it here, installing what it needs without asking. It stays in the foreground with the servers' output; `Ctrl-C` stops them. If it does not get there, deploy from the chat instead.
49
49
 
50
50
  `turri regenerate`, `spec-diff` and `conform` end a build that landed with `To try it: turri release <spec>`.
@@ -13,9 +13,9 @@ The suite runs the projects' testing scripts on this machine, as turri does. The
13
13
 
14
14
  The `mutation` workflow (`.github/workflows/mutation.yml`) runs it on GitHub, started by hand: Actions, mutation, Run workflow. It takes the parts to run, space-separated (empty for every part), each a job of its own side by side, on four cores (`TURRI_MUTATION_CONCURRENCY`; six here). Each part's `mutation.html` and `mutation.json` are uploaded as an artifact, `mutation-<part>`, and summed up on the run's page. Stryker's incremental file is cached from run to run, so a run tests again only the mutants whose code or tests changed; tick `fresh` to test every one again.
15
15
 
16
- Working on turri itself, `npm link` points the global `turri` at this clone, so edits take effect immediately. `npm run chat` starts pi with the extension loaded from `src/`.
16
+ Working on turri itself, `npm link` points the global `turri` at this clone, so edits take effect immediately. `npm run chat` starts [pi](https://pi.dev) with the extension loaded from `src/`.
17
17
 
18
- The package on npm is built: Node runs no `.ts` file inside a `node_modules`, so `npm run build` (`scripts/build.mjs`, over `tsconfig.build.json`) compiles `src/` into `dist/`, file for file - each file as deep as its source, so it finds `genome/`, `stdlib/` and `.pi/` the same way - with its imports' `.ts` rewritten to `.js`, and copies the other files in `src/` beside them. `npm pack` and `npm publish` run it first (`prepack`), and the package holds `dist/`, not `src/`. `bin/turri.mjs` runs `src/cli.ts` where there is one and `dist/cli.js` otherwise; `cli.ts` starts pi with the launcher and extension beside itself, whichever they are. To try the package as users get it: `npm pack`, then `npm install -g ./turri-dev-cli-<version>.tgz`.
18
+ The package on npm is built: Node runs no `.ts` file inside a `node_modules`, so `npm run build` (`scripts/build.mjs`, over `tsconfig.build.json`) compiles `src/` into `dist/`, file for file - each file as deep as its source, so it finds `genome/`, `stdlib/` and `.pi/` the same way - with its imports' `.ts` rewritten to `.js`, and copies the other files in `src/` beside them. `npm pack` and `npm publish` run it first (`prepack`), and the package holds `dist/`, not `src/`. `bin/turri.mjs` runs `src/cli.ts` where there is one and `dist/cli.js` otherwise; `cli.ts` starts [pi](https://pi.dev) with the launcher and extension beside itself, whichever they are. To try the package as users get it: `npm pack`, then `npm install -g ./turri-dev-cli-<version>.tgz`.
19
19
 
20
20
  ## Publishing
21
21
 
@@ -33,16 +33,16 @@ The workflow signs in to npm with npm's trusted publishing (OIDC), not a token:
33
33
  | `bin/turri.mjs` | the launcher: checks the Node version, then loads `src/cli.ts` |
34
34
  | `src/cli.ts` | the `turri` command and its subcommands |
35
35
  | `src/setup.ts`, `src/doctor-tool.ts`, `src/environment.ts`, `src/stack.ts` | `turri doctor` and the chat's `doctor` tool, and what they check - this machine, and in a project what its modules need of it |
36
- | `src/extension.ts` | the pi extension: every tool and command the conversation has |
36
+ | `src/extension.ts` | the [pi](https://pi.dev) extension: every tool and command the conversation has |
37
37
  | `src/skill.ts` | parses `SKILL.md` into steps and gates |
38
38
  | `src/graph.ts` | compiles a skill into a LangGraph `StateGraph`: step -> gate -> route |
39
39
  | `src/judge.ts` | runs independent judges, applies the quorum, builds the critique |
40
40
  | `src/genome.ts` | `runSkill()` - the one entry point the CLI and the extension share |
41
- | `src/pi.ts` | pi session factories for executors, sub-agents and judges |
41
+ | `src/pi.ts` | [pi](https://pi.dev) session factories for executors, sub-agents and judges |
42
42
  | `src/spec-tools.ts` | `write_spec_diff` |
43
43
  | `src/loop-tools.ts` | `regenerate`, `drift` and `halt` - one definition each, for the conversation and the skills alike |
44
44
  | `src/mature-tools.ts` | `mature`'s own two: `watch_jobs` and `spec_findings` |
45
- | `src/plain/` | the \*\*\*plain parser - see [parser](parser.md) - and `highlight.ts`, which highlights \*\*\*plain with plyn's grammar, `plain.tmLanguage.json` (`npm run grammar:sync` refreshes it from [plyn](https://github.com/plainlang/plyn)) |
45
+ | `src/plain/` | the [\*\*\*plain](https://plainlang.org/docs/) parser - see [parser](parser.md) - and `highlight.ts`, which highlights \*\*\*plain with plyn's grammar, `plain.tmLanguage.json` (`npm run grammar:sync` refreshes it from [plyn](https://github.com/plainlang/plyn)) |
46
46
  | `src/regenerate/conformance.ts` | conformance tests: writing them, and fixing the test or the code until they pass |
47
47
  | `src/regenerate/regenerate.ts`, `src/regenerate/conform.ts` | a code job - a spec diff rendered in the code - and a conformance job |
48
48
  | `src/regenerate/loop.ts`, `src/regenerate/records.ts`, `src/regenerate/drift.ts`, `src/regenerate/jobs.ts` | the work loop, the records and the work derived from them, and background jobs |
@@ -54,7 +54,7 @@ The workflow signs in to npm with npm's trusted publishing (OIDC), not a token:
54
54
  | `src/regenerate/engine.ts` | choosing and resolving render models |
55
55
  | `src/regenerate/job-panel.ts`, `src/regenerate/transcript-view.ts`, `src/regenerate/diff-view.ts` | the job panel, the side panel's colours, and a spec diff as they show it - highlighted and banded |
56
56
  | `src/regenerate/side-panel.ts`, `src/regenerate/panel-dock.ts` | the side panel, and the chat narrowed beside it when it is docked |
57
- | `src/screen-padding.ts` | the blank row above and below the whole screen and the two columns either side, in both of pi's ways of drawing it |
57
+ | `src/screen-padding.ts` | the blank row above and below the whole screen and the two columns either side, in both of [pi](https://pi.dev)'s ways of drawing it |
58
58
  | `src/regenerate/spec-map.ts`, `src/regenerate/spec-map-view.ts` | regen: each item's mark, from the records, the `.plain` files and the work loop's plan (`mapPlan`), and the map drawn |
59
59
  | `src/regenerate/debug-log.ts` | each render agent's whole conversation, streamed to `.turri/debug/` - while its pulse runs, and kept in debug mode |
60
60
  | `src/regenerate/pulse-files.ts` | what a pulse writes only for a person - its logs, outputs, temporary files, its chain's installed dependencies - removed as it ends outside debug mode |
@@ -67,4 +67,4 @@ The workflow signs in to npm with npm's trusted publishing (OIDC), not a token:
67
67
  - The executor keeps one session across steps and retries, so context builds up with attempts. Long skills want compaction or a session per step.
68
68
  - A gate's escalation lives in memory (LangGraph's `MemorySaver`): it must be answered in the same run.
69
69
  - The gate machinery has no automated coverage; see [trying the gate semantics](skills-and-gates.md#trying-the-gate-semantics).
70
- - Jobs live in memory: there is no resuming a job after pi exits. The work itself is on disk, so a killed job leaves a partial build rather than nothing.
70
+ - Jobs live in memory: there is no resuming a job after [pi](https://pi.dev) exits. The work itself is on disk, so a killed job leaves a partial build rather than nothing.
package/docs/headless.md CHANGED
@@ -11,8 +11,8 @@ turri doctor
11
11
  Everything marked `✗` stops the run, so fix those first. Two more things matter for a long run:
12
12
 
13
13
  - **The toolchains.** The agents use this machine's: run `turri doctor` in the project, and it says which toolchain a module's implementation reqs name that this machine lacks.
14
- - **The models.** In a project, `turri doctor` sends each model `config.turri.yaml` names one short request, as turri's agents will - so a key the provider refuses, a model pi does not know or an account without access to it shows now, not an hour into the run. Models the run is given on the command line are given to doctor the same way - `--harness-model`, `--coding-model`, `--judge-model`, read as the config's keys - and are checked with the config's.
15
- - **Credentials that do not expire mid-run.** An API key in the environment (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, ...) or pi's saved login - whatever `doctor` shows as the model provider.
14
+ - **The models.** In a project, `turri doctor` sends each model `config.turri.yaml` names one short request, as turri's agents will - so a key the provider refuses, a model [pi](https://pi.dev) does not know or an account without access to it shows now, not an hour into the run. Models the run is given on the command line are given to doctor the same way - `--harness-model`, `--coding-model`, `--judge-model`, read as the config's keys - and are checked with the config's.
15
+ - **Credentials that do not expire mid-run.** An API key in the environment (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, ...) or [pi](https://pi.dev)'s saved login - whatever `doctor` shows as the model provider.
16
16
 
17
17
  Check the spec parses, without spending anything:
18
18
 
@@ -169,7 +169,7 @@ A module that `requires` others is built in its own codebase on top of theirs: t
169
169
  }
170
170
  ```
171
171
 
172
- **Usage.** Every model response an agent gets counts, in a pulse or not. `total` is `in_pulses` plus `outside_pulses`, and `in_pulses` is the pulses' own usage added up - the figures their lines in the notes and the pulse logs print - so the totals can be checked by hand against them. `llm_calls` counts model responses, a compaction's included; `agent_turns` the harness's requests to an agent, each as many responses as it takes; `model_seconds` the time from each request to its response's end, added up - several agents working at once each count theirs, so it can exceed the run's duration. Tokens and cost are as the provider reports them to pi. `by_model`, `by_role` and `by_pulse_kind` split the same calls; a role or model turri could not tell is `unknown`.
172
+ **Usage.** Every model response an agent gets counts, in a pulse or not. `total` is `in_pulses` plus `outside_pulses`, and `in_pulses` is the pulses' own usage added up - the figures their lines in the notes and the pulse logs print - so the totals can be checked by hand against them. `llm_calls` counts model responses, a compaction's included; `agent_turns` the harness's requests to an agent, each as many responses as it takes; `model_seconds` the time from each request to its response's end, added up - several agents working at once each count theirs, so it can exceed the run's duration. Tokens and cost are as the provider reports them to [pi](https://pi.dev). `by_model`, `by_role` and `by_pulse_kind` split the same calls; a role or model turri could not tell is `unknown`.
173
173
 
174
174
  **Pulses.** `landed`: its work is committed. `rolled_back`: it ended without landing - failed, stopped, or did not pass - and everything it wrote was undone. A pulse that failed is tried once more before it counts as failed; both tries are the one pulse.
175
175
 
@@ -177,7 +177,7 @@ A module that `requires` others is built in its own codebase on top of theirs: t
177
177
 
178
178
  **Spec gaps.** A behaviour the spec leaves open that an agent met and decided on its own - reported with `report_spec_gap` (see [regenerating](regenerating.md)). In the chat they are put to you as the pulse ends; a headless run has no one to ask, so they are listed here, in the order the pulses started, and printed under the pulse that met them. `assumption` is what the agent decided meanwhile, also in the codebase's notes' assumptions while its pulse is `landed`; a pulse rolled back takes it out of the notes, but the question still stands. Settle the ones you care about in the spec and run again.
179
179
 
180
- **Turn incidents.** Each time the model provider failed for a moment and pi or turri waited it out before trying the request again, each time the provider sent nothing back on a request for two minutes, and each turn turri stopped itself - an agent going round in a circle, its calls failing again and again, its time up - after which the run carried on. The provider failing or stalling is printed as it happens, in orange, under the pulse; a stop is the work's normal course and is only listed. They are counted in the usage as `provider_retries`, `provider_stalls` and `turns_stopped`. Many of any point at the model, not the spec: a provider at its limits, or a model too small for the role it was given.
180
+ **Turn incidents.** Each time the model provider failed for a moment and [pi](https://pi.dev) or turri waited it out before trying the request again, each time the provider sent nothing back on a request for two minutes, and each turn turri stopped itself - an agent going round in a circle, its calls failing again and again, its time up - after which the run carried on. The provider failing or stalling is printed as it happens, in orange, under the pulse; a stop is the work's normal course and is only listed. They are counted in the usage as `provider_retries`, `provider_stalls` and `turns_stopped`. Many of any point at the model, not the spec: a provider at its limits, or a model too small for the role it was given.
181
181
 
182
182
  **Completed functional specs** - the pass rate, the figure to compare runs by. A functional spec is completed when the code record of the codebase the run delivers - the furthest-built of the chain, the one `--build-dir` exports - as last committed, holds it as the spec has it now: the same text, every file it links holding the same, no definition it uses changed, and not marked to regenerate again. A record moves only when a pulse lands, and a pulse lands only once the code's unit tests pass (its conformance tests too, when they are on), so completed means landed and passing. Each module counts its own functional specs: a module below the tip counts from what the tip's code was built on, and a module above the delivered codebase has none completed. `conformance_tested` is those of the completed ones whose conformance tests are recorded as written for them as they are now (`plain_modules/<module>/tests/.turri.json`).
183
183
 
@@ -206,7 +206,7 @@ turri regenerate app.plain --harness-model amazon-bedrock/eu.anthropic.claude-op
206
206
  --start-over --build-dir /out/build --report /out/report.json < /dev/null
207
207
  ```
208
208
 
209
- - **What it needs:** Node 22.19 or newer, `bash`, `git` (each module's history, and the spec diffs the work is planned from), `/bin/sh` with `kill` and `sleep`, a writable `HOME` (pi keeps `~/.pi/agent/auth.json` there, even with no login - or set `PI_CODING_AGENT_DIR`) and `TMPDIR`, and the toolchains the project's testing scripts and implementation reqs name. `turri doctor` in the project, given the run's models, checks them all and that each model answers - at the start of the task's log, not when a pulse fails:
209
+ - **What it needs:** Node 22.19 or newer, `bash`, `git` (each module's history, and the spec diffs the work is planned from), `/bin/sh` with `kill` and `sleep`, a writable `HOME` ([pi](https://pi.dev) keeps `~/.pi/agent/auth.json` there, even with no login - or set `PI_CODING_AGENT_DIR`) and `TMPDIR`, and the toolchains the project's testing scripts and implementation reqs name. `turri doctor` in the project, given the run's models, checks them all and that each model answers - at the start of the task's log, not when a pulse fails:
210
210
 
211
211
  ```bash
212
212
  turri doctor --harness-model amazon-bedrock/eu.anthropic.claude-opus-5-5 \
@@ -216,13 +216,13 @@ turri regenerate app.plain --harness-model amazon-bedrock/eu.anthropic.claude-op
216
216
  It exits 1 on anything marked `✗`.
217
217
  - **Without bubblewrap:** a container that cannot make user namespaces - no `--security-opt seccomp=unconfined` - has no OS sandbox. turri runs anyway, the command check and the audit holding alone under the agents' commands; `turri doctor` shows it as `!`, not `✗`. Installing the `bubblewrap` package is worth it where it can start.
218
218
  - **As another user:** run it as the user who will own `plain_modules/`. git refuses a repository another user made (`dubious ownership`), so do not let root create it first.
219
- - **Amazon Bedrock:** `AWS_BEARER_TOKEN_BEDROCK` (or the usual AWS credentials) and `AWS_REGION` are enough - no pi login. An `eu.` inference profile needs an EU region. A model id pi does not know stops the first pulse; `turri --list-models bedrock` lists the ones it does.
219
+ - **Amazon Bedrock:** `AWS_BEARER_TOKEN_BEDROCK` (or the usual AWS credentials) and `AWS_REGION` are enough - no [pi](https://pi.dev) login. An `eu.` inference profile needs an EU region. A model id [pi](https://pi.dev) does not know stops the first pulse; `turri --list-models bedrock` lists the ones it does.
220
220
  - **A project with only a `config.yaml`:** it is read while there is no `config.turri*.yaml` - `unittests-script`, `conformance-tests-script` and `prepare-environment-script` included, relative to the project; the keys turri does not know are ignored. Do not write a `config.turri.yaml` beside it to turn on conformance tests or review: from then on `config.yaml` is not read. The options above do it instead.
221
221
  - **Long test suites:** a testing script run is stopped after 120 seconds; set `TURRI_COMMAND_TIMEOUT` (seconds) for more.
222
222
 
223
223
  ## Asking in words instead
224
224
 
225
- pi's print mode runs one request and exits, with the render tools waiting for their work rather than starting background jobs:
225
+ [pi](https://pi.dev)'s print mode runs one request and exits, with the render tools waiting for their work rather than starting background jobs:
226
226
 
227
227
  ```bash
228
228
  nohup turri -p "Render app.plain as a whole module, with conformance tests" > render.log 2>&1 &
package/docs/hosting.md CHANGED
@@ -8,15 +8,15 @@
8
8
 
9
9
  | export | what it is |
10
10
  | --- | --- |
11
- | `createTurriExtension(host)` | turri's conversation as an extension of pi's: every tool, slash command, rule and skill the terminal has. The host is told of its jobs and asks its questions - see below. |
12
- | `pi` | pi itself, as turri's extension is built against it. Make the session with this one - `pi.createAgentSession`, `pi.DefaultResourceLoader` - not another copy. |
11
+ | `createTurriExtension(host)` | turri's conversation as an extension of [pi](https://pi.dev)'s: every tool, slash command, rule and skill the terminal has. The host is told of its jobs and asks its questions - see below. |
12
+ | `pi` | [pi](https://pi.dev) itself, as turri's extension is built against it. Make the session with this one - `pi.createAgentSession`, `pi.DefaultResourceLoader` - not another copy. |
13
13
  | `readProjectConfig`, `projectModels`, `modelSpec`, `configForSpec`, `hasProjectConfig`, `stacksOf` | the project's settings, as [config.turri.yaml](config.md) has them. |
14
14
  | `debugMode` | whether turri keeps, in a project, what it writes only for debugging it - `debug:`, or `TURRI_DEBUG` over it (see [debug mode](config.md#debug-mode)). |
15
- | `initConfig(dir, { vendor, bedrock, models, force, catalogue })`, `VENDORS`, `BEDROCK_REGIONS`, `BEDROCK_DEFAULT` | a project's first config.turri.yaml, as `turri init-config` writes it - async, it may read pi's catalogue - see [a project's first config](#a-projects-first-config). |
16
- | `piModelCatalogue`, `bedrockMakers`, `bedrockModelsIn`, `bedrockDefaultModels`, `bedrockModels`, `bedrockPlaceOf`, `makerLabel`, `BEDROCK_GEOGRAPHIES`, `BEDROCK_IN_REGION` | pi's model catalogue, read without a request, and Bedrock in it: whose models it serves, in which geographies, which two turri offers for a maker and geography. |
15
+ | `initConfig(dir, { vendor, bedrock, models, force, catalogue })`, `VENDORS`, `BEDROCK_REGIONS`, `BEDROCK_DEFAULT` | a project's first config.turri.yaml, as `turri init-config` writes it - async, it may read [pi](https://pi.dev)'s catalogue - see [a project's first config](#a-projects-first-config). |
16
+ | `piModelCatalogue`, `bedrockMakers`, `bedrockModelsIn`, `bedrockDefaultModels`, `bedrockModels`, `bedrockPlaceOf`, `makerLabel`, `BEDROCK_GEOGRAPHIES`, `BEDROCK_IN_REGION` | [pi](https://pi.dev)'s model catalogue, read without a request, and Bedrock in it: whose models it serves, in which geographies, which two turri offers for a maker and geography. |
17
17
  | `bedrockAwsRegion`, `bedrockNeedsAwsRegion` | the AWS region to set `AWS_REGION` to for a Bedrock geography, and whether it has to be set. |
18
18
  | `checkPlainFile`, `plainFileParser`, `resolveTemplateDirs`, `iterateFunctionalSpecs`, `FUNCTIONAL_REQUIREMENTS` | a spec checked and parsed, its functional specs one by one - as `turri validate` reads it. |
19
- | `highlightLines`, `roleOf` | a spec's lines coloured by turri's own \*\*\*plain grammar. |
19
+ | `highlightLines`, `roleOf` | a spec's lines coloured by turri's own [\*\*\*plain](https://plainlang.org/docs/) grammar. |
20
20
  | `functionalityCompletion`, `createWorkLoop`, `createJobRegistry`, `promptOf`, `describeModules` | how far each functional spec is built, and - on a loop of the host's own with `dryRun` - the work a spec change would plan, as `turri glimpse` does. |
21
21
  | `onModelEvent`, `describeTurnIncident` | a provider failing and waited out, a turn turri ended itself. |
22
22
  | `onLedgerEvent`, `readLedger`, `ledgerDir`, `runViews`, `pulseView`, `describePulseView`, `describeRuns`, `ledgerProjects`, `earlierPulses`, `asJob` | what turri did, as facts - the [ledger](using.md#the-ledger): each event heard as it is recorded, and every run and pulse read back, earlier sessions' too - see [the ledger in a host](#the-ledger-in-a-host). |
@@ -40,11 +40,11 @@ const { written, message } = await initConfig(project, { models: { harness, codi
40
40
  `initConfig` writes config.turri.yaml as `turri init-config` does, and returns what it did - `written`, and `message`, the lines the terminal prints. It is async.
41
41
 
42
42
  - `vendor` - `anthropic`, `openai`, `google`, `amazon-bedrock` or `other` - fills in that vendor's own two models (`VENDORS` has each one's label and models).
43
- - `bedrock: { maker?, region }`, on Amazon Bedrock, says whose and where. turri's own pair is written where it has one - Anthropic's and OpenAI's in `BEDROCK_REGIONS` - and otherwise the pair `bedrockDefaultModels` offers from pi's catalogue: any maker and geography it has.
44
- - `models: { harness, coding, strong? }` writes those two instead - any pair pi's catalogue has as chat models, full specs without a thinking level (`amazon-bedrock/global.xai.grok-4.7`). They go wherever the vendor's own would: the two models with the thinking turri writes, the commented-out `judge-model` and `models:` examples, `strong` - the judging examples' model - being `harness` unless given. The vendor, the Bedrock maker and the geography are read from them, so neither `vendor` nor `bedrock` is needed. A model the catalogue does not have, or that is not a chat model, writes nothing: `written` is false and `message` says which, with the closest the catalogue has.
43
+ - `bedrock: { maker?, region }`, on Amazon Bedrock, says whose and where. turri's own pair is written where it has one - Anthropic's and OpenAI's in `BEDROCK_REGIONS` - and otherwise the pair `bedrockDefaultModels` offers from [pi](https://pi.dev)'s catalogue: any maker and geography it has.
44
+ - `models: { harness, coding, strong? }` writes those two instead - any pair [pi](https://pi.dev)'s catalogue has as chat models, full specs without a thinking level (`amazon-bedrock/global.xai.grok-4.7`). They go wherever the vendor's own would: the two models with the thinking turri writes, the commented-out `judge-model` and `models:` examples, `strong` - the judging examples' model - being `harness` unless given. The vendor, the Bedrock maker and the geography are read from them, so neither `vendor` nor `bedrock` is needed. A model the catalogue does not have, or that is not a chat model, writes nothing: `written` is false and `message` says which, with the closest the catalogue has.
45
45
  - `catalogue`, the one the host already read, saves reading it again.
46
46
 
47
- The message says whose models were written - "with xAI's models on Amazon Bedrock (global)" - and how to sign in, with the `AWS_REGION` for the geography: `bedrockAwsRegion(geography)` is that region, and `bedrockNeedsAwsRegion(geography)` whether it has to be set - every geography but `global`, `us` and `in-region`, pi calling Bedrock in `us-east-1` unless `AWS_REGION` or the profile names another.
47
+ The message says whose models were written - "with xAI's models on Amazon Bedrock (global)" - and how to sign in, with the `AWS_REGION` for the geography: `bedrockAwsRegion(geography)` is that region, and `bedrockNeedsAwsRegion(geography)` whether it has to be set - every geography but `global`, `us` and `in-region`, [pi](https://pi.dev) calling Bedrock in `us-east-1` unless `AWS_REGION` or the profile names another.
48
48
 
49
49
  ## doctor in a host
50
50
 
@@ -91,8 +91,8 @@ await session.bindExtensions({ uiContext, mode: "tui" });
91
91
  - **The jobs.** The pulses the conversation starts - its tools, `/regenerate`, a spec change - run in its own job registry and work loop, the project's one. The host is handed both as they are made, and told of each job as it changes, so it shows the very jobs the conversation runs. Its buttons are best the conversation's commands - `session.prompt("/regenerate app.plain")` - so there is never a second loop on the same project.
92
92
  - **The questions.** `dialogs` is `ask_user`'s two dialogs: `select(title, options, context, at)` and `input(title, context)`, each resolving with the answer, or undefined when the person dismissed it. Given, the conversation's questions and a skill's interview are put in them; without it, in the terminal's own dialog.
93
93
  - **The surface.** What turri tells the model of where the person watches the work is the terminal's by default: regen, the side panel on `ctrl+h`, `/pulses`. In a host none of that is there, and the model would send the person to keys and panels that do not exist. `surface` is the host's own description of its UI - the views it has in place of the terminal's, what its marks and buttons mean, where questions appear - in its own words. Given, turri leaves every terminal instruction out of what the model is told and puts the surface in turri's identity, as a `<surface>` block, where the identity speaks of regen. It reaches the side questions and the tools' own words too: a pulse not found says the `pulses` tool lists them, not `/pulses`. Text only the person sees in the terminal - the job panel, the side panel - is the terminal's and stays as it is. Without `surface`, the prompt is the terminal's, byte for byte.
94
- - **The system prompt.** `systemPrompt(prompt)` gets the whole system prompt for the turn - pi's, turri's identity and blocks, the rules - after turri's own `before_agent_start` work, and returns what is sent. It is for what `surface` does not cover; to describe the UI, use `surface`, rather than words appended after turri's that contradict them.
95
- - **The mode.** `mode: "tui"` runs the work in the background and keeps the conversation free, as the terminal does; `rpc`, `json` and `print` wait for each job. The extension's other UI calls - its notices, its working line, a confirm - come to the host's `uiContext`, as pi's [SDK](https://github.com/earendil-works/pi) describes it.
94
+ - **The system prompt.** `systemPrompt(prompt)` gets the whole system prompt for the turn - [pi](https://pi.dev)'s, turri's identity and blocks, the rules - after turri's own `before_agent_start` work, and returns what is sent. It is for what `surface` does not cover; to describe the UI, use `surface`, rather than words appended after turri's that contradict them.
95
+ - **The mode.** `mode: "tui"` runs the work in the background and keeps the conversation free, as the terminal does; `rpc`, `json` and `print` wait for each job. The extension's other UI calls - its notices, its working line, a confirm - come to the host's `uiContext`, as [pi](https://pi.dev)'s [SDK](https://github.com/earendil-works/pi) describes it.
96
96
 
97
97
  A surface for a desktop app might read:
98
98
 
@@ -1,6 +1,6 @@
1
1
  # Installation
2
2
 
3
- **turri** runs wherever pi runs - macOS, Linux, WSL - with git, bash and Node 22.19 or newer (a Linux distribution's own `nodejs` package is often older: take it from nodejs.org, nvm or NodeSource). Native Windows is best effort: it needs bash on the PATH (Git for Windows' Git Bash), and has no OS sandbox under the agents' commands.
3
+ **turri** runs wherever [pi](https://pi.dev) runs - macOS, Linux, WSL - with git, bash and Node 22.19 or newer (a Linux distribution's own `nodejs` package is often older: take it from nodejs.org, nvm or NodeSource). Native Windows is best effort: it needs bash on the PATH (Git for Windows' Git Bash), and has no OS sandbox under the agents' commands.
4
4
 
5
5
  The agents' commands run on this machine, with its toolchains - see [the sandbox](sandbox.md). Each is checked before it runs and audited after, on every platform; under them an OS sandbox fences it where the OS has one: Seatbelt on macOS, always there, and bubblewrap on Linux and WSL 2 - `apt install bubblewrap`, or your distribution's package.
6
6
 
@@ -17,7 +17,7 @@ turri init-config # a config.turri.yaml with the model you talk to and the one
17
17
  turri # pi, with turri's tools, skills and themes loaded
18
18
  ```
19
19
 
20
- The package is `@turri-dev/cli` - npm keeps the name `turri` back, as too like another package's - and the command it puts on your PATH is `turri`. `npm install -g @turri-dev/cli@next` gets the latest prerelease instead, built from the last commit on `main`. Installed again - `@latest` or `@next` - it replaces the one there. `turri --version` says which turri runs, and the pi under it: `turri 0.1.6 (pi 1.3.0)`, and from a clone its commit too.
20
+ The package is `@turri-dev/cli` - npm keeps the name `turri` back, as too like another package's - and the command it puts on your PATH is `turri`. `npm install -g @turri-dev/cli@next` gets the latest prerelease instead, built from the last commit on `main`. Installed again - `@latest` or `@next` - it replaces the one there. `turri --version` says which turri runs, and the [pi](https://pi.dev) under it: `turri 0.1.6 (pi 1.3.0)`, and from a clone its commit too.
21
21
 
22
22
  ## From a clone
23
23
 
@@ -31,7 +31,7 @@ npm link # puts `turri` on your PATH, pointing at this clone
31
31
 
32
32
  A clone runs its TypeScript sources directly - Node strips the types - so edits take effect immediately. The package on npm runs JavaScript compiled from them instead (`npm run build`, into `dist/`, run by `npm pack` and `npm publish` before they pack), as Node runs no `.ts` file inside a `node_modules`. `bin/turri.mjs` runs `src/` where there is one, and `dist/` otherwise.
33
33
 
34
- To update, `git pull` and `npm install` - the second picks up a dependency that changed, pi's say. `npm link` needs running only once. A **turri** already running keeps the code it started with: start it again to run what you pulled.
34
+ To update, `git pull` and `npm install` - the second picks up a dependency that changed, [pi](https://pi.dev)'s say. `npm link` needs running only once. A **turri** already running keeps the code it started with: start it again to run what you pulled.
35
35
 
36
36
  ## In Docker
37
37
 
@@ -43,12 +43,12 @@ It checks, in order, and says exactly what to run for anything missing:
43
43
 
44
44
  | what | why | if it is missing |
45
45
  | --- | --- | --- |
46
- | Node 22.19+ | pi needs 22.19, as does a clone running turri's `.ts` sources directly | install a newer Node |
46
+ | Node 22.19+ | [pi](https://pi.dev) needs 22.19, as does a clone running turri's `.ts` sources directly | install a newer Node |
47
47
  | bash | every command an agent runs, and every testing script, runs with it | on Windows, Git for Windows; elsewhere, the system's package manager |
48
48
  | the OS sandbox | the wall under the command check and the audit | macOS has Seatbelt; on Linux, `apt install bubblewrap`. Where it does not start it says why and how to give it one - user namespaces off (Docker: a user that is not root, `--security-opt seccomp=unconfined`), AppArmor in the way (Docker: `--security-opt apparmor=unconfined`; Ubuntu 24.04: `sysctl -w kernel.apparmor_restrict_unprivileged_userns=0`), Docker's masked `/proc` (`--security-opt systempaths=unconfined`, or nothing: turri binds the container's `/proc` instead). Without one, the check and the audit hold alone |
49
49
  | a model provider | the agents need a model | run `pi` and `/login`, or set an API key (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, ...) |
50
50
  | `turri` on PATH | so it runs from any project | `npm install -g @turri-dev/cli`, or `npm link` in a clone |
51
- | turri and `pi` apart | `turri` starts pi with turri; a plain `pi` stays pi | an older `turri setup` wrote a file that loads turri into every `pi` - `doctor` offers to remove it |
51
+ | turri and `pi` apart | `turri` starts [pi](https://pi.dev) with turri; a plain `pi` stays [pi](https://pi.dev) | an older `turri setup` wrote a file that loads turri into every `pi` - `doctor` offers to remove it |
52
52
 
53
53
  In a project - a folder with `config.turri.yaml` (or the old `config.yaml`) or `plain_modules` - it checks what the project needs of this machine too:
54
54
 
@@ -56,7 +56,7 @@ In a project - a folder with `config.turri.yaml` (or the old `config.yaml`) or `
56
56
  | --- | --- |
57
57
  | each module's toolchains | what its implementation reqs name - Python, Node, Java, Go, Rust, C/C++, Zig, ... - against this machine's PATH: the agents use this machine's toolchains and install none, so a missing one is yours to install |
58
58
  | the testing scripts | what in them this machine does not have: a program named by its whole path that is not here (a macOS-only `/usr/libexec/java_home` on Linux), a toolchain they run that is not on the PATH |
59
- | the models | each model `config.turri.yaml` names - `harness-model`, `coding-model`, `judge-model`, every role under `models:` - sent one short request through pi, with the credentials turri's agents will use, for any provider pi supports. One that answers is ready; for one that does not, it says what the provider said and what to do: the credentials pi reads for that provider (`AWS_PROFILE` and `AWS_REGION` for Amazon Bedrock, `OPENAI_API_KEY`, `/login`, ...), model access in the Bedrock console, the closest model ids pi knows for one it does not. `--harness-model`, `--coding-model` (`--model`) and `--judge-model` name models as the config's keys, as `turri regenerate` reads them - checked in a project or not. `--no-models` skips it |
59
+ | the models | each model `config.turri.yaml` names - `harness-model`, `coding-model`, `judge-model`, every role under `models:` - sent one short request through [pi](https://pi.dev), with the credentials turri's agents will use, for any provider [pi](https://pi.dev) supports. One that answers is ready; for one that does not, it says what the provider said and what to do: the credentials [pi](https://pi.dev) reads for that provider (`AWS_PROFILE` and `AWS_REGION` for Amazon Bedrock, `OPENAI_API_KEY`, `/login`, ...), model access in the Bedrock console, the closest model ids [pi](https://pi.dev) knows for one it does not. `--harness-model`, `--coding-model` (`--model`) and `--judge-model` name models as the config's keys, as `turri regenerate` reads them - checked in a project or not. `--no-models` skips it |
60
60
 
61
61
  Removing that file is the one thing `doctor` changes, and it asks first; `turri doctor --yes` says yes. Run without a terminal - piped, or in CI - it asks nothing and changes nothing. Run it again whenever something breaks; `turri doctor --no-models` leaves out the models' requests. In the chat, ask for a self check: the `doctor` tool runs the same checks, the models only when you ask. A host - a desktop app - runs the same checks through `doctor` and shows them itself (see [doctor in a host](hosting.md#doctor-in-a-host)).
62
62
 
@@ -64,9 +64,9 @@ The chat is told every turn which wall is up and what the project's modules need
64
64
 
65
65
  ## turri and `pi`
66
66
 
67
- `turri` runs pi from this project's `node_modules`, with turri loaded, so no global pi install is needed. A plain `pi` is left as it is: starting pi does not start turri, and turri puts nothing into pi's own folders.
67
+ `turri` runs [pi](https://pi.dev) from this project's `node_modules`, with turri loaded, so no global [pi](https://pi.dev) install is needed. A plain `pi` is left as it is: starting [pi](https://pi.dev) does not start turri, and turri puts nothing into [pi](https://pi.dev)'s own folders.
68
68
 
69
- An older `turri setup` could load turri into every `pi`, with a one-line file in `~/.pi/agent/extensions/` pointing at this clone. `turri doctor` says when it is there, and offers to remove it. Until it is gone, `turri` leaves out its own `-e`: pi treats a tool registered twice as an error.
69
+ An older `turri setup` could load turri into every `pi`, with a one-line file in `~/.pi/agent/extensions/` pointing at this clone. `turri doctor` says when it is there, and offers to remove it. Until it is gone, `turri` leaves out its own `-e`: [pi](https://pi.dev) treats a tool registered twice as an error.
70
70
 
71
71
  `turri` starts in the `turri-dark` theme. A plain `pi` keeps its own theme; to give it `turri-dark` too, in `~/.pi/agent/settings.json`:
72
72
 
@@ -76,18 +76,18 @@ An older `turri setup` could load turri into every `pi`, with a one-line file in
76
76
 
77
77
  ### Project trust
78
78
 
79
- pi will not auto-discover a project-local extension until you trust the project, and `-p` has no way to ask - so `npm run chat` and `npm run ask` pass `-e` explicitly. Once you have run `pi` interactively in this directory and accepted the trust prompt, plain `pi` picks the extension up on its own. A `defaultProjectTrust` key in `.pi/settings.json` does *not* work - a project cannot vouch for itself; that setting only counts in `~/.pi/agent/settings.json`.
79
+ [pi](https://pi.dev) will not auto-discover a project-local extension until you trust the project, and `-p` has no way to ask - so `npm run chat` and `npm run ask` pass `-e` explicitly. Once you have run `pi` interactively in this directory and accepted the trust prompt, plain `pi` picks the extension up on its own. A `defaultProjectTrust` key in `.pi/settings.json` does *not* work - a project cannot vouch for itself; that setting only counts in `~/.pi/agent/settings.json`.
80
80
 
81
81
  ## Environment
82
82
 
83
- Models and credentials come from your pi configuration (`~/.pi/agent`) or provider environment variables - the same resolution `pi` itself uses. Put the rest in your shell profile:
83
+ Models and credentials come from your [pi](https://pi.dev) configuration (`~/.pi/agent`) or provider environment variables - the same resolution `pi` itself uses. Put the rest in your shell profile:
84
84
 
85
85
  ```bash
86
86
  export PLAIN_TEMPLATE_DIRS=~/path/to/standard_template_library # shared templates
87
87
  export GEMINI_API_KEY=... # and leave GOOGLE_API_KEY unset
88
88
  ```
89
89
 
90
- For Gemini, `GEMINI_API_KEY` is what makes pi *offer* the google provider, but when `GOOGLE_API_KEY` is also set pi uses that one for the calls and says so.
90
+ For Gemini, `GEMINI_API_KEY` is what makes [pi](https://pi.dev) *offer* the google provider, but when `GOOGLE_API_KEY` is also set [pi](https://pi.dev) uses that one for the calls and says so.
91
91
 
92
92
  | variable | |
93
93
  | --- | --- |
package/docs/parser.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # The \*\*\*plain parser
2
2
 
3
- `src/plain/` is a TypeScript port of the reference \*\*\*plain parser - `plain_file.py`, `plain_spec.py`, `concept_utils.py`, and the `file_utils.py` helpers they depend on.
3
+ `src/plain/` is a TypeScript port of the reference [\*\*\*plain](https://plainlang.org/docs/) parser - `plain_file.py`, `plain_spec.py`, `concept_utils.py`, and the `file_utils.py` helpers they depend on.
4
4
 
5
5
  | module | ports |
6
6
  |---|---|
@@ -96,7 +96,7 @@ While it works, the agent runs tests however it likes with `bash` - one file, on
96
96
  - **The static checks fail** - when the unit testing script runs them, it exits 80 for the tests passing and its checks not, and the agent is sent back saying so, with the findings (see [the static checks](#the-static-checks)). They share the same 20 rounds - a static check is part of what "the unit tests pass" means here, not a gate after it.
97
97
  - **It wrote nothing.** A turn can end with the agent having only thought the problem through, and the code folder as it found it (notes aside). That leaves nothing to test or review. So before the tests run, the agent is told to stop planning and write a draft: its best guess at what the spec asks, the code and its unit tests, with any assumption put in the notes. A draft that is wrong comes back from the tests and the review; nothing written cannot. It is asked once, to write the change or say why it needs no code: one that only rewords a spec may need none. After that, the turn is taken as what it did.
98
98
 
99
- **Long output.** A testing script's run is shown whole up to 16,000 characters. Past that the agent is shown its failures, each with the 20 lines after it - its trace - its counts (`Tests run:`, `Ran 3 tests`, `BUILD FAILURE`) and its last 80 lines, with DEBUG and TRACE logging left out and each gap marked: on the hycu runs, 73% less to carry, every failing run's failures still there. Any other output an agent is shown is whole up to 60,000 characters, and past that its end. The whole of it is kept in `plain_modules/.turri/logs/`, and the cut names it by an id: the agent reads it with `read_output` - a range of lines, or the lines matching a pattern, each with its number - which reads those kept outputs and nothing else. A command in its shell cut short by pi is kept the same way. No output points at a path: the file it is in is outside what the agent may reach.
99
+ **Long output.** A testing script's run is shown whole up to 16,000 characters. Past that the agent is shown its failures, each with the 20 lines after it - its trace - its counts (`Tests run:`, `Ran 3 tests`, `BUILD FAILURE`) and its last 80 lines, with DEBUG and TRACE logging left out and each gap marked: on the hycu runs, 73% less to carry, every failing run's failures still there. Any other output an agent is shown is whole up to 60,000 characters, and past that its end. The whole of it is kept in `plain_modules/.turri/logs/`, and the cut names it by an id: the agent reads it with `read_output` - a range of lines, or the lines matching a pattern, each with its number - which reads those kept outputs and nothing else. A command in its shell cut short by [pi](https://pi.dev) is kept the same way. No output points at a path: the file it is in is outside what the agent may reach.
100
100
 
101
101
  A test may go only when it asserts nothing meaningful - it cannot fail, exactly duplicates another, or tests something the spec does not require. Inconvenient, slow or failing are not grounds. Justified removals are reported, not silent. The baseline is taken before the agent starts, so breaking an earlier functionality's test is caught too.
102
102
 
@@ -274,7 +274,7 @@ Either way the pulse is not tried again: no second run would find what is missin
274
274
 
275
275
  Every job is a spec diff: the agent that renders it is given the spec the code is built from - its functional specs in `<already implemented>` - and the change to it, in `<spec change>`, section by section - not the raw diff: each of `<definitions>`, `<implementation reqs>` and `<functional specs>` in its own tag, `<definitions change="1 added, 2 changed">`, saying what is added, what changed with its text before and now, and what is removed, measured from what is built to the spec the job ends on. A definition is itself by its concept: one reworded or moved is shown as changed, not as removed and added. Test reqs and acceptance tests are for conformance only: the code record holds neither, and no coding agent is shown them.
276
276
 
277
- **Every part of the spec is given with its module.** A codebase is built from the spec of its whole requires chain, and each module numbers its functional specs from 1, so every agent is shown each section grouped under the module each item belongs to - `<module name="base">`, then `<module name="app" role="current">` - as `<already implemented>` groups the functional specs (`moduleSection`, `functionalitiesByModule` in `prompt.ts`). A module's own items, and what it imports, are the current module's. `<spec change>` names a functional spec as "functionality 2 of module `app`", and a definition or req of a module below with its module; each case line does too ("fs 2 of module `app`"). The primer every agent reads first says that modules are how the \*\*\*plain spec is written, not how the code is laid out: the agent works only in the current module's codebase, which holds the code of every module it requires, and organises the code as the implementation reqs say.
277
+ **Every part of the spec is given with its module.** A codebase is built from the spec of its whole requires chain, and each module numbers its functional specs from 1, so every agent is shown each section grouped under the module each item belongs to - `<module name="base">`, then `<module name="app" role="current">` - as `<already implemented>` groups the functional specs (`moduleSection`, `functionalitiesByModule` in `prompt.ts`). A module's own items, and what it imports, are the current module's. `<spec change>` names a functional spec as "functionality 2 of module `app`", and a definition or req of a module below with its module; each case line does too ("fs 2 of module `app`"). The primer every agent reads first says that modules are how the [\*\*\*plain](https://plainlang.org/docs/) spec is written, not how the code is laid out: the agent works only in the current module's codebase, which holds the code of every module it requires, and organises the code as the implementation reqs say.
278
278
 
279
279
  It is told, too, what to do with each functional spec the job touches, by its case (`src/regenerate/cases.ts`): a fresh project is set up first, with its unit tests where the unit testing script runs them; a new functional spec is built and unit tested; a changed one - its text, or a concept it uses - is changed in place, its code and tests to match, never built again from scratch; one regenerated again is replaced, not patched - rewritten or started over, what it replaces taken out; one taken out has its code and the tests only it had removed. A job holding several cases has a line for each. A job that touches no functional spec - implementation reqs, or definitions none uses - is mechanical: the code adapts, no new test is written, and an existing one may change only mechanically, never what it asserts. The unit test review holds each part to its case, and a mechanical job only to its reqs and to no test removed or weakened. A conformance job's agent and its reviewer are told the same, for the tests:
280
280
 
@@ -295,7 +295,7 @@ A functional spec is patched when a concept it uses changed as well as when its
295
295
 
296
296
  **Where a linked resource goes in the code.** A linked resource is not in the agent's workspace; it reaches the agent only in `<linked resources>`. Each text resource is given a `code-copy` - its path, without steps up - and the agent is told that when the code needs what the resource holds while it runs, it keeps exactly one copy there (or where the implementation reqs put such files) and reads it from there, and otherwise copies none, so one file is not written to every place it might be read from.
297
297
 
298
- **Linked images.** A linked `.png`, `.jpg` or `.jpeg` is an image, not text (`resources.ts`). The coding agent and the conformance agent are sent it with their first message - and again after each compaction, which sums the conversation up without its images - and `<linked resources>` lists it with its type and its number among the attachments (`<resource name="checkout mockup" path="resources/checkout.png" type="image/png">`). Their model has to read images: a job whose spec links one refuses a model that does not before any agent starts (`refuseIfModelIsBlind`; `createExecutor` refuses pi's default model the same way), saying which model and to set `coding-model` to one that reads images or describe the image in text. An image over 5 MB stops the job, naming the file. The reviewers do not see linked resources, text or images. An image is hashed like any linked file, so a new mockup patches the functional specs that link it.
298
+ **Linked images.** A linked `.png`, `.jpg` or `.jpeg` is an image, not text (`resources.ts`). The coding agent and the conformance agent are sent it with their first message - and again after each compaction, which sums the conversation up without its images - and `<linked resources>` lists it with its type and its number among the attachments (`<resource name="checkout mockup" path="resources/checkout.png" type="image/png">`). Their model has to read images: a job whose spec links one refuses a model that does not before any agent starts (`refuseIfModelIsBlind`; `createExecutor` refuses [pi](https://pi.dev)'s default model the same way), saying which model and to set `coding-model` to one that reads images or describe the image in text. An image over 5 MB stops the job, naming the file. The reviewers do not see linked resources, text or images. An image is hashed like any linked file, so a new mockup patches the functional specs that link it.
299
299
 
300
300
  A job lands once its unit tests pass and are reviewed: the code record becomes the spec the job ends on, committed with the code. Its conformance tests are not the code job's: they are a track of their own ([conformance tests in the work loop](#conformance-tests-in-the-work-loop)).
301
301
 
@@ -411,7 +411,7 @@ An agent's task - the spec, how to read it, what to do, the rules, the working n
411
411
 
412
412
  The agents that work through a job keep **one session for it**: the coding agent of a render or a spec change, its unit test reviewer, the conformance agent, its test reviewer, the code fixer of a conformance run, and the regression repair agent. The code fixer used to start fresh for every fix; now each fix starts from what the ones before it tried. The agents whose value is having taken no side stay fresh, given what they need as text: the arbiter, the judge that says whether the code, the tests or the spec is wrong, the explorer, the planners, the diagnosis and a skill's review judges. A job that fails and is retried is undone whole, and every agent of the retry starts fresh.
413
413
 
414
- An agent kept through a job re-reads everything it has done on every call. So between turns, once its conversation - what it has done, past its instructions - passes `compact-at` tokens (100,000 unless [the project config](config.md) says otherwise), the harness compacts it, with instructions for its role: the coding agent and the fixer keep what is done, what still fails and why, the decisions and every review problem's fate; a reviewer keeps every problem it raised and where each stands, and what it has already checked. pi's own compaction, when the context window is nearly full, stays on beneath it.
414
+ An agent kept through a job re-reads everything it has done on every call. So between turns, once its conversation - what it has done, past its instructions - passes `compact-at` tokens (100,000 unless [the project config](config.md) says otherwise), the harness compacts it, with instructions for its role: the coding agent and the fixer keep what is done, what still fails and why, the decisions and every review problem's fate; a reviewer keeps every problem it raised and where each stands, and what it has already checked. [pi](https://pi.dev)'s own compaction, when the context window is nearly full, stays on beneath it.
415
415
 
416
416
  What each agent of a job did - its turns, its compactions, its tokens and cost - is at the end of the job's log in `.turri/jobs/` (kept in [debug mode](config.md#debug-mode)), and in `pulses`.
417
417
 
package/docs/sandbox.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # The sandbox
2
2
 
3
- A rendering agent is building one module. It has no business reading the `.plain` specs it was not given, the rest of the project, or anything else on the machine - and pi's `cwd` is only where relative paths start: an absolute path resolves to itself and `bash` can `cd` anywhere. So turri fences its agents: their file tools check every path, and every command they run on this machine passes a command check, runs in an environment of the module's own behind an OS sandbox where the OS has one, and is audited afterwards. The rules live in code that runs the same on every platform; the OS sandbox is an extra wall under them.
3
+ A rendering agent is building one module. It has no business reading the `.plain` specs it was not given, the rest of the project, or anything else on the machine - and [pi](https://pi.dev)'s `cwd` is only where relative paths start: an absolute path resolves to itself and `bash` can `cd` anywhere. So turri fences its agents: their file tools check every path, and every command they run on this machine passes a command check, runs in an environment of the module's own behind an OS sandbox where the OS has one, and is audited afterwards. The rules live in code that runs the same on every platform; the OS sandbox is an extra wall under them.
4
4
 
5
5
  ## What an agent can reach
6
6
 
@@ -14,7 +14,7 @@ Each agent's file tools reach its own module and only the parts its role needs:
14
14
  | Explorers | its module's `code/` | nothing |
15
15
  | Test review | its module's `tests/` and `code/` | nothing |
16
16
 
17
- Nothing else exists for them: not another module, not turri's scratch area `plain_modules/.turri` (a test run's whole output reaches them through `read_output`, not by path), not the module's lineage `.plaingit`, and nothing above `plain_modules`. The specs it is building from reach it only through the prompt the harness writes. Nor does any context file: pi puts every `AGENTS.md` / `CLAUDE.md` from an agent's folder up into its system prompt, and turri turns that off for every agent it starts - so the project's own `AGENTS.md`, or yours in `~/.pi/agent`, never reaches a rendering agent, a judge or a skill's agent. Nor do pi's skills, which pi would list for an agent to load: each agent turri starts has one job, and turri runs its own gated skills itself. The conversation you talk to still loads both, as pi does - but for the skills turri has already, its own and any about \*\*\*plain (see [a project's own skills](skills-and-gates.md#a-projects-own-skills)).
17
+ Nothing else exists for them: not another module, not turri's scratch area `plain_modules/.turri` (a test run's whole output reaches them through `read_output`, not by path), not the module's lineage `.plaingit`, and nothing above `plain_modules`. The specs it is building from reach it only through the prompt the harness writes. Nor does any context file: [pi](https://pi.dev) puts every `AGENTS.md` / `CLAUDE.md` from an agent's folder up into its system prompt, and turri turns that off for every agent it starts - so the project's own `AGENTS.md`, or yours in `~/.pi/agent`, never reaches a rendering agent, a judge or a skill's agent. Nor do [pi](https://pi.dev)'s skills, which [pi](https://pi.dev) would list for an agent to load: each agent turri starts has one job, and turri runs its own gated skills itself. The conversation you talk to still loads both, as [pi](https://pi.dev) does - but for the skills turri has already, its own and any about [\*\*\*plain](https://plainlang.org/docs/) (see [a project's own skills](skills-and-gates.md#a-projects-own-skills)).
18
18
 
19
19
  ## Every agent knows its walls
20
20
 
@@ -33,7 +33,7 @@ No other path is named - nothing into `plain_modules/.turri`. A test run or a co
33
33
 
34
34
  ## File tools
35
35
 
36
- `src/regenerate/confine.ts` rebuilds `read`, `write`, `edit`, `ls`, `grep` and `find` from pi's own factories with a check in front of every path. The check runs twice: once on the argument the tool was called with, and once on the absolute path the tool resolved it to, because the two can differ. Symlinks are resolved before comparing, so a link cannot walk out, and a glob containing `..` is refused outright.
36
+ `src/regenerate/confine.ts` rebuilds `read`, `write`, `edit`, `ls`, `grep` and `find` from [pi](https://pi.dev)'s own factories with a check in front of every path. The check runs twice: once on the argument the tool was called with, and once on the absolute path the tool resolved it to, because the two can differ. Symlinks are resolved before comparing, so a link cannot walk out, and a glob containing `..` is refused outright.
37
37
 
38
38
  The record of what the code is built from, `code/.turri.json`, is the harness's: no agent reads or writes it. `read` refuses it, and `ls`, `grep` and `find` leave it out of what they list - as the command check, the OS sandbox and the audit keep it from commands.
39
39
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  A skill's review step is normally a paragraph asking the agent to check its own work. Agents talk their way past paragraphs. turri makes the review an edge in a graph instead: the harness decides when a gate runs, spawns the judges itself, and branches on a structured verdict. The model's only job is producing the artifact under review.
4
4
 
5
- pi supplies the agent runtime (sessions, tools, multi-provider models); LangGraph supplies the control flow. The gate syntax lives in `SKILL.md`, so a gated skill still loads in any spec-compliant harness - it just runs ungated there.
5
+ [pi](https://pi.dev) supplies the agent runtime (sessions, tools, multi-provider models); LangGraph supplies the control flow. The gate syntax lives in `SKILL.md`, so a gated skill still loads in any spec-compliant harness - it just runs ungated there.
6
6
 
7
7
  ## The syntax
8
8
 
@@ -73,17 +73,17 @@ A gate can also have a `continuous` reviewer: a read-only agent that re-reads th
73
73
 
74
74
  ## What a step can do
75
75
 
76
- - **Interview the user.** `ask_user` puts one question to the person. In the TUI it is a dialog; from the CLI the options are numbered. Offering choices is required, not optional - a call with neither `options` nor `free_form: true` is refused - and every list carries "Something else…". `multi_select: true` gives every option a box, ticked and unticked as it is chosen - a pick made by mistake is undone by choosing it again - until they choose Done; what they type is one more box, ticked. "Something else…", "Chat about this" and Done are no boxes. The skill's agent talks in a session of its own that the person never sees, so what the person must read to answer - the brief to confirm, the plan to approve - it passes as `ask_user`'s `context`, shown above the question in the dialog; `brood`, `spawn` and `bud` pass their briefs so. The agents are told to keep it to what can be taken in at a glance, about fifteen lines; one longer scrolls in a window of its own above the question (`src/question-dialog.ts`) - PgUp/PgDn, Shift+Up/Down or the mouse wheel - so the question and its options always show below it. pi's own select, which turri used before, put it all in its title, and a long brief pushed the options off the screen. Nothing else it wrote is shown there: the last thing it said before asking - its account of a probe, a regeneration - is kept with the answer, in the run's trace and the transcript, never in the dialog.
76
+ - **Interview the user.** `ask_user` puts one question to the person. In the TUI it is a dialog; from the CLI the options are numbered. Offering choices is required, not optional - a call with neither `options` nor `free_form: true` is refused - and every list carries "Something else…". `multi_select: true` gives every option a box, ticked and unticked as it is chosen - a pick made by mistake is undone by choosing it again - until they choose Done; what they type is one more box, ticked. "Something else…", "Chat about this" and Done are no boxes. The skill's agent talks in a session of its own that the person never sees, so what the person must read to answer - the brief to confirm, the plan to approve - it passes as `ask_user`'s `context`, shown above the question in the dialog; `brood`, `spawn` and `bud` pass their briefs so. The agents are told to keep it to what can be taken in at a glance, about fifteen lines; one longer scrolls in a window of its own above the question (`src/question-dialog.ts`) - PgUp/PgDn, Shift+Up/Down or the mouse wheel - so the question and its options always show below it. [pi](https://pi.dev)'s own select, which turri used before, put it all in its title, and a long brief pushed the options off the screen. Nothing else it wrote is shown there: the last thing it said before asking - its account of a probe, a regeneration - is kept with the answer, in the run's trace and the transcript, never in the dialog.
77
77
 
78
78
  Beside "Something else…", every list an agent's question shows carries "Chat about this": the person says what they want to talk over - why it is asked, what an option means for later - and it goes back to the agent that asked, as no answer. A skill's agent answers in the `context` of the question asked again, since nothing else it writes reaches them; the conversation's agent answers in its reply and ends its turn, the person going on in the chat. It is not counted against the questioning level, and owes no spec change. The harness's own first question - how many the phases may ask - has no agent to answer, and does not offer it. Either choice is backed out of with Esc, or by typing nothing: the options come back, so one picked by accident costs nothing. Esc on the options themselves dismisses the question.
79
- - **Be asked something on the side.** A skill runs inside one `cultivate` call, so the conversation can answer nothing until it returns. `/btw <question>` (`btw.ts`) can: pi runs a command at once, even mid-turn, and it starts an agent of its own - fresh, on the chat's model, reading the project and turri and changing nothing - shown the conversation, the running skill's trace and the end of its agent's session, and the pulses. It is told it is turri, as the conversation's agent is (`identity.ts`), and answers as turri, in the first person - never as an onlooker who sends the person to turri with a message to pass on. Its answer goes in the transcript as an entry, in no model's context: the conversation and the skill go on as if nothing was asked. While a question of the skill's has the keys, `/btw <question>` typed as the answer (under "Something else…") is the same: answered, and the question asked again with the answer above it - the skill's agent never sees it.
79
+ - **Be asked something on the side.** A skill runs inside one `cultivate` call, so the conversation can answer nothing until it returns. `/btw <question>` (`btw.ts`) can: [pi](https://pi.dev) runs a command at once, even mid-turn, and it starts an agent of its own - fresh, on the chat's model, reading the project and turri and changing nothing - shown the conversation, the running skill's trace and the end of its agent's session, and the pulses. It is told it is turri, as the conversation's agent is (`identity.ts`), and answers as turri, in the first person - never as an onlooker who sends the person to turri with a message to pass on. Its answer goes in the transcript as an entry, in no model's context: the conversation and the skill go on as if nothing was asked. While a question of the skill's has the keys, `/btw <question>` typed as the answer (under "Something else…") is the same: answered, and the question asked again with the answer above it - the skill's agent never sees it.
80
80
  - **Build while it interviews.** `write_spec_diff` (`brood`, `spawn`) writes a change to the spec and queues its render in one call; the interview carries on while the builds run in the background, one after another. `drift` shows how they stand. The person sees its calls as `mutate`: a tool can be shown by a name of its own (`SHOWN_AS` in `regenerate/activity.ts`), while its agent calls it by the one that says what it takes.
81
81
  - **Regenerate, and conform.** A skill that names them in `allowed-tools` gets the conversation's own `regenerate`, `drift`, `halt` and `conform`, on its loop: `conform` schedules a conformance pulse for functional specs not built yet, as it does from the chat. The skill never waits for them.
82
82
  - **Choose the render model**, when the user says so - `engine`, the same tool the conversation has.
83
83
 
84
84
  The harness still owns the sequence. A step chooses what to ask; it does not choose whether the gate afterwards runs. A run is saved as it goes, so an interrupted one can be resumed where it stopped.
85
85
 
86
- While a skill runs in the chat, `cultivate`'s output leads with what its agent is doing now: the skill, its step and how long each has taken, its last few tool calls, and the end of what it is saying, refreshed every few seconds. pi shows only a running tool's first lines, and a step can take many minutes without a new line in the trace. The result the conversation gets back keeps the trace alone.
86
+ While a skill runs in the chat, `cultivate`'s output leads with what its agent is doing now: the skill, its step and how long each has taken, its last few tool calls, and the end of what it is saying, refreshed every few seconds. [pi](https://pi.dev) shows only a running tool's first lines, and a step can take many minutes without a new line in the trace. The result the conversation gets back keeps the trace alone.
87
87
 
88
88
  ## A project's own skills
89
89
 
@@ -91,11 +91,11 @@ A project can have skills of its own, in the same format: a folder a skill, with
91
91
 
92
92
  A name is one skill. turri's own come first, then `.turri/skills/`, then `.agents/skills/`; a skill whose name is taken already is left out, and the list says so - a project's `brood` never quietly stands in for turri's. A skill that does not parse is said to have failed to load, by its folder.
93
93
 
94
- `.turri/` is turri's scratch folder and ignores itself, all but `skills/`: its `.gitignore` is `*`, `!skills/`, `!skills/**`, and one written before is brought up to date. pi reads `.agents/skills/` too, as skills of its own the chat could follow by hand - the chat is told to run any `cultivate` lists with `cultivate`, so its gates still run.
94
+ `.turri/` is turri's scratch folder and ignores itself, all but `skills/`: its `.gitignore` is `*`, `!skills/`, `!skills/**`, and one written before is brought up to date. [pi](https://pi.dev) reads `.agents/skills/` too, as skills of its own the chat could follow by hand - the chat is told to run any `cultivate` lists with `cultivate`, so its gates still run.
95
95
 
96
- The skills pi offers the chat leave out what turri has already (`src/chat-skills.ts`): a skill of a name turri runs itself - its own, or the project's - and any skill about \*\*\*plain, its specs or their testing scripts. pi gathers skills from `~/.pi/agent/skills`, `~/.agents/skills` and the `.agents/skills` of every folder from where turri started up to its git root, so a folder above the project can hold another tool's \*\*\*plain skills - a `load-plain-reference` the chat would load at the start of every session, though the whole \*\*\*plain reference, turri's [rules](#rules), is in its system prompt already. The person is told once which were left out. A skill about anything else is offered as pi offers it.
96
+ The skills [pi](https://pi.dev) offers the chat leave out what turri has already (`src/chat-skills.ts`): a skill of a name turri runs itself - its own, or the project's - and any skill about [\*\*\*plain](https://plainlang.org/docs/), its specs or their testing scripts. [pi](https://pi.dev) gathers skills from `~/.pi/agent/skills`, `~/.agents/skills` and the `.agents/skills` of every folder from where turri started up to its git root, so a folder above the project can hold another tool's \*\*\*plain skills - a `load-plain-reference` the chat would load at the start of every session, though the whole \*\*\*plain reference, turri's [rules](#rules), is in its system prompt already. The person is told once which were left out. A skill about anything else is offered as [pi](https://pi.dev) offers it.
97
97
 
98
- A skill is found once, as turri starts: one added while it runs is there the next time. A project's skill agent runs as turri's do - pi's file tools and `bash` on the machine, no fence - so a project's skills are as trusted as the project.
98
+ A skill is found once, as turri starts: one added while it runs is there the next time. A project's skill agent runs as turri's do - [pi](https://pi.dev)'s file tools and `bash` on the machine, no fence - so a project's skills are as trusted as the project.
99
99
 
100
100
  ## Rules
101
101
 
package/docs/theming.md CHANGED
@@ -8,7 +8,7 @@ turri --use-theme turri-light/turri-dark # follows terminal appearance
8
8
  turri --use-theme dark # pi's own
9
9
  ```
10
10
 
11
- The second form follows your terminal's light/dark appearance. The default is for `turri` only: pi's settings are left alone, so a plain `pi` keeps its theme. With the themes copied to `~/.pi/agent/themes/` (`cp .pi/themes/*.json ~/.pi/agent/themes/`), `{"theme": "turri-dark"}` in `~/.pi/agent/settings.json` gives a plain `pi` the same one.
11
+ The second form follows your terminal's light/dark appearance. The default is for `turri` only: [pi](https://pi.dev)'s settings are left alone, so a plain `pi` keeps its theme. With the themes copied to `~/.pi/agent/themes/` (`cp .pi/themes/*.json ~/.pi/agent/themes/`), `{"theme": "turri-dark"}` in `~/.pi/agent/settings.json` gives a plain `pi` the same one.
12
12
 
13
13
  Bad input fails quietly - an unknown `--use-theme` name and a malformed theme file both run without complaint - so if the colours do not change, suspect a typo rather than the mechanism.
14
14
 
@@ -26,7 +26,7 @@ Both themes, the command line's colours, the spec diff's, the banner's jellyfish
26
26
  | path | `#3e5a6b` | the editor's border, what is dim: `dim`, line numbers |
27
27
  | grauzone | `#85a3b2` | body text, and what can be skimmed: `text`, `muted` |
28
28
  | white | `#f4f7f9` | headings, tool titles, what you typed |
29
- | siesta tan | `#e9d8c8` | strings in code, \*\*\*plain's text in quotes or backticks |
29
+ | siesta tan | `#e9d8c8` | strings in code, [\*\*\*plain](https://plainlang.org/docs/)'s text in quotes or backticks |
30
30
  | eggplant | `#732553` | a removed line's fill in a spec diff, the jellyfish's rim |
31
31
  | neon | `#5ce1e6` | links, focus, the selection, section labels, \*\*\*plain's concepts in a spec diff: `accent`, `mdLink`, regen's `◆` |
32
32
  | neon hover | `#8eebee` | inline code - siesta tan beside white text was too close to tell apart, and tiring to read - types in code, an added line's fill in a light theme's spec diff: `mdCode` |
@@ -74,4 +74,4 @@ A spec diff in the panels is not painted with the theme's colours for code: thos
74
74
 
75
75
  ## The font
76
76
 
77
- The design system's fonts cannot be applied. pi draws into a terminal, so the typeface is your terminal emulator's setting. Its monospaced face is Geist Mono - set it in iTerm2 / Ghostty / Alacritty for the brand look; Neue Montreal, for headings, is proportional, and a terminal needs a monospaced face.
77
+ The design system's fonts cannot be applied. [pi](https://pi.dev) draws into a terminal, so the typeface is your terminal emulator's setting. Its monospaced face is Geist Mono - set it in iTerm2 / Ghostty / Alacritty for the brand look; Neue Montreal, for headings, is proportional, and a terminal needs a monospaced face.
package/docs/using.md CHANGED
@@ -7,7 +7,7 @@ cd ~/my-project
7
7
  turri
8
8
  ```
9
9
 
10
- That is pi, with turri's tools, skills and themes loaded - `turri` alone, or with pi's options (`turri -c`, `turri --harness-model <m>`). It opens with turri's own banner - the jellyfish it is named after, regenerating until the conversation fills the screen: a pixel polyp on the sea floor grows into the jellyfish - a bell from pink down to indigo, an orange gut, neon tentacles - which swims a while, sinks back to the floor, and the polyp grows again, round and round: a picture in a terminal that shows images (Kitty, Ghostty, WezTerm, Warp, iTerm2), and drawn at a third of the size in half blocks in any other, tmux included (`src/jellyfish.ts` draws it, ported from the design system's pixel kit, `scripts/jellyfish-frames.mjs` writes the pictures) - turri's version (and, run from a clone, the commit it is on, and whether the clone has changes) and where its settings are changed - and only when something is wrong, skills that failed to load or agents' commands less fenced than they should be - rather than pi's startup header and resource list; `turri --verbose` shows those too. pi's "Update Available" notice is left out - turri's pi is a pinned dependency `pi update` never touches - and the terminal's tab reads `🪼 turri - <folder>`, not pi's `π`. Talk to it: ask it to add a feature to a spec, render a module, fix a spec. It decides which skill or tool applies - but not *how* a skill runs: `cultivate` hands the work to the gated runner, which owns the steps, the gates and the judges (see [skills and gates](skills-and-gates.md)).
10
+ That is [pi](https://pi.dev), with turri's tools, skills and themes loaded - `turri` alone, or with [pi](https://pi.dev)'s options (`turri -c`, `turri --harness-model <m>`). It opens with turri's own banner - the jellyfish it is named after, regenerating until the conversation fills the screen: a pixel polyp on the sea floor grows into the jellyfish - a bell from pink down to indigo, an orange gut, neon tentacles - which swims a while, sinks back to the floor, and the polyp grows again, round and round: a picture in a terminal that shows images (Kitty, Ghostty, WezTerm, Warp, iTerm2), and drawn at a third of the size in half blocks in any other, tmux included (`src/jellyfish.ts` draws it, ported from the design system's pixel kit, `scripts/jellyfish-frames.mjs` writes the pictures) - turri's version (and, run from a clone, the commit it is on, and whether the clone has changes) and where its settings are changed - and only when something is wrong, skills that failed to load or agents' commands less fenced than they should be - rather than [pi](https://pi.dev)'s startup header and resource list; `turri --verbose` shows those too. [pi](https://pi.dev)'s "Update Available" notice is left out - turri's [pi](https://pi.dev) is a pinned dependency `pi update` never touches - and the terminal's tab reads `🪼 turri - <folder>`, not [pi](https://pi.dev)'s `π`. Talk to it: ask it to add a feature to a spec, render a module, fix a spec. It decides which skill or tool applies - but not *how* a skill runs: `cultivate` hands the work to the gated runner, which owns the steps, the gates and the judges (see [skills and gates](skills-and-gates.md)).
11
11
 
12
12
  Three skills you start yourself, by their commands - turri never starts them on its own, and says which to type when one fits: `/brood`, `/spawn` and `/bud`, each with what you want after it - `/spawn a URL shortener in Go`. The skills that build from scratch:
13
13
 
@@ -49,7 +49,7 @@ What each render does, in order, is in [regenerating the code](regenerating.md).
49
49
 
50
50
  The conversation knows the ***plain standard library from its first turn: the stdlib rule in its system prompt lists every stack and where the templates are, so "build this as a Flask backend" becomes an import of `stdlib/backend/python-flask`, not a stack written out by hand. The banner at start says how many stacks there are, and **`/stdlib`** lists them, each with what it is (see [the standard library](authoring-plain.md#the-standard-library)).
51
51
 
52
- The agent does this work with these tools, never by running turri's command line - or another pi - through its shell: that would be a separate, headless run outside regen and `/halt`. The harness refuses such a command unless your message asks for the command line ("run it headless", "use the CLI", or the command itself). pi configuring itself - `pi install`, `update`, `list`, `config`, `auth`, its settings - is not affected.
52
+ The agent does this work with these tools, never by running turri's command line - or another [pi](https://pi.dev) - through its shell: that would be a separate, headless run outside regen and `/halt`. The harness refuses such a command unless your message asks for the command line ("run it headless", "use the CLI", or the command itself). [pi](https://pi.dev) configuring itself - `pi install`, `update`, `list`, `config`, `auth`, its settings - is not affected.
53
53
 
54
54
  ## Pulses
55
55
 
@@ -59,8 +59,8 @@ A render takes minutes, so in the TUI every render tool starts the work as a pul
59
59
 
60
60
  - **The panel** above the input, while something runs, shows on top what finished - a line a module, its pulses oldest first, each with its mark (green, red or amber, as in the side panel), number and kind - what it regenerated is in the side panel - `cognio … 2 earlier · ✓ 3 regenerate · ✓ 4 conformance`, the newest that fit the width - then a rule, and under it each running pulse - `cognio · regenerate pulse 5`, `cognio · conformance pulse 6` - its time and cost, and what its agents are doing. A module whose work is being planned into pulses shows at once, before any pulse runs - `shorty · planning the pulses · 12s` - the planner is an agent, and takes a while; a `/regenerate` says it began the moment it is sent. Pulses are numbered within their module, as regen numbers them. `ctrl+o` expands the agents' trace alone; what finished stays a line a module. The line of keys under it - `ctrl+o to expand the trace · ctrl+h to peek at regen` - has each key breathing, fully there to a little faint and back, until it is first pressed: once `ctrl+o` has opened the trace, or the side panel has been opened, that key is still for the rest of the session and the other breathes on. What waits is not in it, and it shows no spec diff - the side panel has both. With nothing running it is gone.
61
61
  - **A pulse's change** is what its agent works from, not a text diff of the module's file: the items `<spec change>` gives the agent - only what the pulse's functional specs use, an import's items as well as the module's own, a code pulse's without the test reqs - each in the file it is written in, under its section, in the file's order (`change-view.ts`).
62
- - **A spec diff**, in the side panel, reads as an editor shows a diff: its \*\*\*plain highlighted as [plyn](https://github.com/plainlang/plyn) highlights it - section headers, concepts, names in backticks, text in quotes, links, the front matter - each added line filled green and each removed one red, the text on top, the fill the width of the panel ([colours](theming.md#spec-diffs)). It is shown file by file, each file's lines together under its name, with what it adds and takes out - the module being rendered first, the rest by name, and none the diff does not change. Each line has its number in that file: a spec diff is of the spec as read, with what it imports laid in, so a line is placed in the file it is written in - its links read as the parser renders them - and a removed line, in no file now, goes with the change it is part of and has no number. A line is never put in a file on a guess: one that cannot be placed is listed last, under "not found in the spec's files". Where lines of a file are not in the diff, `↕ 28 lines` says how many, as GitHub does - `↑` above the first - and names the section the lines under it are in when it is another than the one above, `↕ 28 lines · ***functional specs***`, as GitHub names a hunk's function: each section is named once.
63
- - **The side panel** (`ctrl+h`, as it was left - but on the pulse that last failed, stopped or did not pass, when one has since it was last opened: the live panel that showed it is gone with the last pulse, and its announcement in the chat says `ctrl+h shows this pulse`; **`/pulses`** on the pulses; not `ctrl+j`, which a terminal without the Kitty keyboard protocol sends as a line feed, the same as `shift+enter`, and which a dialog takes for `enter`; both stay pi's new-line keys. `ctrl+h` is the byte old terminals sent for backspace; a terminal set to send `^H` for its Backspace key would open the panel on it) takes the whole screen - the panel or the chat, one at a time - or, `s`, sits beside the chat: the chat on the left, the panel in the right half of the screen, and `s` again over the whole screen. It opens as it was last left, for the conversation; a terminal under 104 columns - 100 inside the screen's padding, a blank row above and below everything and two columns either side - has no room for both and shows the panel over the whole screen. It opens on **regen**: the spec itself, in full, with how much of it the code has been regenerated from drawn onto it.
62
+ - **A spec diff**, in the side panel, reads as an editor shows a diff: its [\*\*\*plain](https://plainlang.org/docs/) highlighted as [plyn](https://github.com/plainlang/plyn) highlights it - section headers, concepts, names in backticks, text in quotes, links, the front matter - each added line filled green and each removed one red, the text on top, the fill the width of the panel ([colours](theming.md#spec-diffs)). It is shown file by file, each file's lines together under its name, with what it adds and takes out - the module being rendered first, the rest by name, and none the diff does not change. Each line has its number in that file: a spec diff is of the spec as read, with what it imports laid in, so a line is placed in the file it is written in - its links read as the parser renders them - and a removed line, in no file now, goes with the change it is part of and has no number. A line is never put in a file on a guess: one that cannot be placed is listed last, under "not found in the spec's files". Where lines of a file are not in the diff, `↕ 28 lines` says how many, as GitHub does - `↑` above the first - and names the section the lines under it are in when it is another than the one above, `↕ 28 lines · ***functional specs***`, as GitHub names a hunk's function: each section is named once.
63
+ - **The side panel** (`ctrl+h`, as it was left - but on the pulse that last failed, stopped or did not pass, when one has since it was last opened: the live panel that showed it is gone with the last pulse, and its announcement in the chat says `ctrl+h shows this pulse`; **`/pulses`** on the pulses; not `ctrl+j`, which a terminal without the Kitty keyboard protocol sends as a line feed, the same as `shift+enter`, and which a dialog takes for `enter`; both stay [pi](https://pi.dev)'s new-line keys. `ctrl+h` is the byte old terminals sent for backspace; a terminal set to send `^H` for its Backspace key would open the panel on it) takes the whole screen - the panel or the chat, one at a time - or, `s`, sits beside the chat: the chat on the left, the panel in the right half of the screen, and `s` again over the whole screen. It opens as it was last left, for the conversation; a terminal under 104 columns - 100 inside the screen's padding, a blank row above and below everything and two columns either side - has no room for both and shows the panel over the whole screen. It opens on **regen**: the spec itself, in full, with how much of it the code has been regenerated from drawn onto it.
64
64
 
65
65
  Every item of the spec - a definition, a req, a functional spec with its sub-bullets and acceptance tests, what turri plans and diffs by - is a row in the file's order, its \*\*\*plain highlighted as plyn highlights it (as a spec diff is) and wrapped to the width, never cut short. The map has two columns, each headed on the line over it with a line of space under it: **status**, a mark a row - its rung on a ladder every item climbs - and the module's file, its spec. The top of the panel is where it is seen first: the top line, the module tabs, and the keys - each view's, `d talk it through in the chat` first when the module needs you - wrapped, never cut short, a line of space between the top line and the keys; then straight under them a rule, a line of space, and the columns' headings. The pulses view has its keys at the top too, and a line of space under its rule. The legend is a box of its own in the upper right corner, over what is under it, a line a mark, up the ladder. It names every rung, whatever the page shows - to take out only where something is - and a panel under 60 columns wide has none.
66
66
 
@@ -84,8 +84,8 @@ A render takes minutes, so in the TUI every render tool starts the work as a pul
84
84
 
85
85
  `p` goes to the **pulses** - and back - onto the pulse that regenerates the item under the cursor, and back onto the first item the pulse under the cursor regenerates. The pulses are every pulse as three sections - running, queued, finished - each opened and closed on its own (running and queued start open) - and a fourth, **earlier sessions**, closed, when the [ledger](#the-ledger) has pulses of sessions before this one: its last three runs', each pulse read only - opened, what the ledger says of it (how it ended, its rounds and test runs, the healers, what turri refused and committed, its last stages and its log) - and a line for how many more runs it has. A pulse is one line: its module, kind and number (`hubspot · regenerate pulse 1`, `hubspot · conformance pulse 2`) - so it is named wherever a pulse is named in a line (the job panel, the `pulses` tool, `/halt`, the command line), never by what it regenerates, which is in it, opened - its time (none while it is queued: it has not started) and its cost - a finished one's mark green when it passed, red when it failed or did not pass, amber when it was stopped. Opened, a running one shows what it is doing; a queued one, what it waits for; a finished one, its agents and what each spent, and whether it did not pass. Under it is a row for each file its spec diff changes - the file of the module being rendered first and open, the rest by name and closed - each opening onto that file's lines. A file with nothing added or taken out is not listed. `↑` `↓` move, `enter` or `space` open and close what is under the cursor, `→` `←` open and close (`←` on a closed pulse goes to its section, on a closed file to its pulse), `o` and `c` open or close every pulse of the section - or, on a file, every file of its pulse - `pgup` `pgdn` and the wheel scroll, `g`/`G` go to the top and the bottom. The cursor stays on its row when the pulse under it moves on; a pulse opened closes when it moves to another section. What is open is kept while the panel is closed. What the agents said and did is not shown - it is in each pulse's log (see [Agent logs](#agent-logs)).
86
86
 
87
- `ctrl+h` again, or `esc`, closes it and goes back to the chat. Beside the chat, one side has the keys and the other is drawn faint: `ctrl+h` switches sides - from the chat to the panel, from the panel to the chat, the panel staying and following the pulses - `esc` gives the chat the keys too, and a click gives them to the side clicked. `ctrl+c` closes the panel; so do `s`, over the whole screen, then `esc`. (pi's regular screen, `--tui-mode regular`, reads no mouse: there it is the keys alone.) While the panel has the keys, every key is the panel's, whatever else happens: a question the chat model asks in the background (`ask_user`) waits in the chat, unanswered - an `enter` in the panel never answers it - and the panel's top line says "the chat is waiting for your answer". Back in the chat, the question is there, with the keys.
88
- - `turri` runs pi in fullscreen mode, so the conversation stays where you scroll it while the pulses redraw (PgUp/PgDn, the wheel); `--tui-mode regular` goes back to the terminal's own scrollback.
87
+ `ctrl+h` again, or `esc`, closes it and goes back to the chat. Beside the chat, one side has the keys and the other is drawn faint: `ctrl+h` switches sides - from the chat to the panel, from the panel to the chat, the panel staying and following the pulses - `esc` gives the chat the keys too, and a click gives them to the side clicked. `ctrl+c` closes the panel; so do `s`, over the whole screen, then `esc`. ([pi](https://pi.dev)'s regular screen, `--tui-mode regular`, reads no mouse: there it is the keys alone.) While the panel has the keys, every key is the panel's, whatever else happens: a question the chat model asks in the background (`ask_user`) waits in the chat, unanswered - an `enter` in the panel never answers it - and the panel's top line says "the chat is waiting for your answer". Back in the chat, the question is there, with the keys.
88
+ - `turri` runs [pi](https://pi.dev) in fullscreen mode, so the conversation stays where you scroll it while the pulses redraw (PgUp/PgDn, the wheel); `--tui-mode regular` goes back to the terminal's own scrollback.
89
89
  - **`/turri-config`** changes a setting of the project's `config.turri.yaml` - the models, review, conformance tests, any key - and says when it takes effect: now, at your next message, or at the next pulse; never only after a restart. See [changing it from the chat](config.md#changing-it-from-the-chat).
90
90
  - **`/stack`** chooses the stack everything is built with from then on, in a project whose parts are on different stacks: a list of the project's stacks, or `/stack backend`, `/stack none`. The stack's `config.turri.<stack>.yaml` is laid over `config.turri.yaml` - its testing scripts, toolchains, models - for the session; a module already asked for keeps the stack it was asked for with. The chat model switches it too when you ask - and, to build one part, passes that part's stack to `regenerate` instead, leaving the session's as it is. See [stacks](config.md#stacks).
91
91
  - **`/regenerate [module | file.plain] [functionality ...] [from scratch [whole chain]] [how to cut the work]`** regenerates a module from its spec as it is now, as the `regenerate` tool does: what is left to regenerate is planned into pulses - one functional spec at a time, a few together or the whole module in one, as the planner judges - and the first starts. Words after the module are guidance for that plan (`/regenerate app models first`). A module's name typed with spaces for its underscores is that module - `/regenerate d365_function executor` is `d365_function_executor`. With no module named: the one `module:` in `config.turri.yaml` names, else the folder's top modules, to pick from. Asked for from scratch - `from scratch`, `start over`, `delete the module` - it asks first, then deletes the module's code and conformance tests in every codebase where they live, committed so `/lineage` keeps them, and regenerates the whole module from its spec, built though nothing in it changed: `/regenerate shorty from scratch`. The modules below it keep their code, and the module starts again from a copy of the code below it. Asked for its chain too - `/regenerate shorty from scratch, the whole chain` - the code of every module of its requires chain is deleted, the base's included, and the whole chain is regenerated from the bottom: what to do after modules were split, merged or moved, when the code below was laid out for modules that are no more. The chat's `regenerate` does the same with `from_scratch: "module"` or `"chain"`, when you ask for it; `turri regenerate --from-scratch` or `--from-scratch-chain` on the command line, not with `--dry-run`. Functionalities named after the module - `/regenerate shorty 3 5` - are regenerated though their spec did not change, on the model chosen for functionalities: as `turri regenerate shorty.plain 3 5` does, and the chat's `regenerate` with `functionalities`.
@@ -125,7 +125,7 @@ judge-model:
125
125
 
126
126
  Each may leave out either part, and a bare string (`coding-model: provider/id`, or `provider/id:high`) is a model alone. What one leaves out it takes from the one it inherits from: the judge model from the coding model, the coding model from the harness model - so with only a harness model, everything runs on it.
127
127
 
128
- On the command line, `turri --harness-model <m> --coding-model <m>` wins over them (pi's own `--model` works for the harness model too), and in the conversation pi's `/model` changes the harness model and `engine`, below, the coding model. `model:`, from before there were two, is read as `coding-model`. With neither, renders run on the conversation's model.
128
+ On the command line, `turri --harness-model <m> --coding-model <m>` wins over them ([pi](https://pi.dev)'s own `--model` works for the harness model too), and in the conversation [pi](https://pi.dev)'s `/model` changes the harness model and `engine`, below, the coding model. `model:`, from before there were two, is read as `coding-model`. With neither, renders run on the conversation's model.
129
129
 
130
130
  Every render runs on the coding model unless you say otherwise. Say it in words - "render spec changes with gpt 5.5", "build the functionalities on Gemini 3.6 Flash", "use Claude for everything" - and the agent calls `engine`, which holds for the rest of the session (a resumed one too):
131
131
 
@@ -137,17 +137,17 @@ Every render runs on the coding model unless you say otherwise. Say it in words
137
137
 
138
138
  It works mid-interview as well: the skill's agent has the same tool. Pulses already queued keep the model they were queued with; `"chat"` drops the session's choice, back to the default coding model - `--coding-model`, `config.turri.yaml`, or the conversation's. `/engine` shows what each kind of work runs on. A render tool can also be given a model for one call.
139
139
 
140
- Names are resolved strictly, because pi's own matching guesses and says nothing ("gpt-5" can land on a Bedrock model you have no key for). An exact `provider/model-id` is taken as it is. A looser name is taken when it fits exactly one model you have credentials for, or is exactly one model's id in other words ("gpt 5.5" is `openai/gpt-5.5`, not `gpt-5.5-pro`). Anything else comes back as a short list and the agent asks you which one; a model whose provider has no credentials is never picked.
140
+ Names are resolved strictly, because [pi](https://pi.dev)'s own matching guesses and says nothing ("gpt-5" can land on a Bedrock model you have no key for). An exact `provider/model-id` is taken as it is. A looser name is taken when it fits exactly one model you have credentials for, or is exactly one model's id in other words ("gpt 5.5" is `openai/gpt-5.5`, not `gpt-5.5-pro`). Anything else comes back as a short list and the agent asks you which one; a model whose provider has no credentials is never picked.
141
141
 
142
- A provider a pi extension registers - your company's own LLM gateway, say - works for every agent the conversation starts, not only for the conversation. Those agents load no extensions (they would load a second copy of turri), so each is given the providers the conversation's extensions registered, as they are when it starts. An unattended run - `turri regenerate`, `render` and the rest, outside the chat - has no conversation to take them from: there, only the providers in pi's own configuration (`~/.pi/agent/models.json` and `auth.json`) are there, so a gateway those runs use has to be set up in `models.json`.
142
+ A provider a [pi](https://pi.dev) extension registers - your company's own LLM gateway, say - works for every agent the conversation starts, not only for the conversation. Those agents load no extensions (they would load a second copy of turri), so each is given the providers the conversation's extensions registered, as they are when it starts. An unattended run - `turri regenerate`, `render` and the rest, outside the chat - has no conversation to take them from: there, only the providers in [pi](https://pi.dev)'s own configuration (`~/.pi/agent/models.json` and `auth.json`) are there, so a gateway those runs use has to be set up in `models.json`.
143
143
 
144
- To change pi's own default, in `~/.pi/agent/settings.json`:
144
+ To change [pi](https://pi.dev)'s own default, in `~/.pi/agent/settings.json`:
145
145
 
146
146
  ```json
147
147
  { "defaultProvider": "google", "defaultModel": "gemini-3.1-pro-preview" }
148
148
  ```
149
149
 
150
- A provider failure - no credits, bad key, rate limit - is raised with the provider's own message, rather than surfacing as an agent that said nothing. One that passes - overloaded, rate limited, unreachable, timed out - is waited out first, after pi's own retries: six more tries, the wait doubled each time from 15 seconds up to five minutes (15s, 30s, 1m, 2m, 4m, 5m, a tenth either way so parallel jobs do not all come back at once), the agent carrying on where it was. Each wait - pi's own retries too - is shown as it happens - a notification in the chat and the job panel's status, an orange line in a headless run - and listed in the run report's `turn_incidents`. So is a provider stalling: a request to the model that nothing has come back on for two minutes (`TURRI_PROVIDER_STALL`, in seconds; 0 never), said once for each silence while the turn goes on waiting. A turn turri stops itself - its time up, the agent going round in a circle - is the work's normal course and is not said, only listed.
150
+ A provider failure - no credits, bad key, rate limit - is raised with the provider's own message, rather than surfacing as an agent that said nothing. One that passes - overloaded, rate limited, unreachable, timed out - is waited out first, after [pi](https://pi.dev)'s own retries: six more tries, the wait doubled each time from 15 seconds up to five minutes (15s, 30s, 1m, 2m, 4m, 5m, a tenth either way so parallel jobs do not all come back at once), the agent carrying on where it was. Each wait - [pi](https://pi.dev)'s own retries too - is shown as it happens - a notification in the chat and the job panel's status, an orange line in a headless run - and listed in the run report's `turn_incidents`. So is a provider stalling: a request to the model that nothing has come back on for two minutes (`TURRI_PROVIDER_STALL`, in seconds; 0 never), said once for each silence while the turn goes on waiting. A turn turri stops itself - its time up, the agent going round in a circle - is the work's normal course and is not said, only listed.
151
151
 
152
152
  ## Module history
153
153
 
@@ -165,7 +165,7 @@ git --git-dir=plain_modules/<module>/.plaingit --work-tree=plain_modules/<module
165
165
 
166
166
  When an agent does something you cannot explain, its logs are where to look: the chat shows what its pulse is doing, not what its agents said. Every agent a render starts - the coding agent, the conformance agent, the repair agent, the spec-steps planner - also streams its whole conversation to a file of its own.
167
167
 
168
- The logs are in `.turri/debug/` in the project, beside `plain_modules`, one per agent, named by when it started and what it is: `2026-09-26T13-45-02-123Z-app-3-coding-agent.log`. Each starts with the pulse it runs for, its model and thinking level, its folder, its saved pi session, its tools and its system prompt, and then has, in order and stamped with the time: every prompt it is sent, whole; its thinking and replies as they stream; every tool call with its arguments, numbered
168
+ The logs are in `.turri/debug/` in the project, beside `plain_modules`, one per agent, named by when it started and what it is: `2026-09-26T13-45-02-123Z-app-3-coding-agent.log`. Each starts with the pulse it runs for, its model and thinking level, its folder, its saved [pi](https://pi.dev) session, its tools and its system prompt, and then has, in order and stamped with the time: every prompt it is sent, whole; its thinking and replies as they stream; every tool call with its arguments, numbered
169
169
  - `→ #3 bash` - and its whole result under the same number, `← #3 bash done`, since calls made together run at once and finish in any order; a tool call a response was cut off in, marked as never run; each response's stop reason, tokens and cost; the provider's retries and errors; and how each turn ended. Every line is on disk as it happens, so `tail -f` follows an agent live and a run that crashed leaves its log up to that moment. The pulse's own log names each agent's log as it starts.
170
170
 
171
171
  Beside them, each pulse has a log of its own in `.turri/jobs/` - `<time>-<pulse id>-<kind>.log`: its whole transcript, written as it happens. The harness's stages, the functional spec or the diff it builds, and every one of its agents in turn - each prompt, its thinking and replies, every tool call and its result, whole where the screen clips them - and how the pulse ended. A finished pulse, opened in the side panel, names it. It is the one to read for a pulse as a whole; an agent's own log has its system prompt, tokens and retries besides.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turri-dev/cli",
3
- "version": "0.1.10-next.2abf2db",
3
+ "version": "0.1.11-next.056c7c7",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {