@turri-dev/cli 0.1.4-next.318a98a → 0.1.4-next.4342332

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/.pi/themes/turri-dark.json +67 -66
  2. package/.pi/themes/turri-light.json +71 -73
  3. package/README.md +39 -127
  4. package/assets/jellyfish/dark-00.png +0 -0
  5. package/assets/jellyfish/dark-01.png +0 -0
  6. package/assets/jellyfish/dark-02.png +0 -0
  7. package/assets/jellyfish/dark-03.png +0 -0
  8. package/assets/jellyfish/dark-04.png +0 -0
  9. package/assets/jellyfish/dark-05.png +0 -0
  10. package/assets/jellyfish/dark-06.png +0 -0
  11. package/assets/jellyfish/dark-07.png +0 -0
  12. package/assets/jellyfish/dark-08.png +0 -0
  13. package/assets/jellyfish/dark-09.png +0 -0
  14. package/assets/jellyfish/dark-10.png +0 -0
  15. package/assets/jellyfish/dark-11.png +0 -0
  16. package/assets/jellyfish/dark-12.png +0 -0
  17. package/assets/jellyfish/dark-13.png +0 -0
  18. package/assets/jellyfish/dark-14.png +0 -0
  19. package/assets/jellyfish/light-00.png +0 -0
  20. package/assets/jellyfish/light-01.png +0 -0
  21. package/assets/jellyfish/light-02.png +0 -0
  22. package/assets/jellyfish/light-03.png +0 -0
  23. package/assets/jellyfish/light-04.png +0 -0
  24. package/assets/jellyfish/light-05.png +0 -0
  25. package/assets/jellyfish/light-06.png +0 -0
  26. package/assets/jellyfish/light-07.png +0 -0
  27. package/assets/jellyfish/light-08.png +0 -0
  28. package/assets/jellyfish/light-09.png +0 -0
  29. package/assets/jellyfish/light-10.png +0 -0
  30. package/assets/jellyfish/light-11.png +0 -0
  31. package/assets/jellyfish/light-12.png +0 -0
  32. package/assets/jellyfish/light-13.png +0 -0
  33. package/assets/jellyfish/light-14.png +0 -0
  34. package/dist/ask.js +34 -15
  35. package/dist/brand.js +7 -7
  36. package/dist/cli-options.js +12 -6
  37. package/dist/config-edit.js +1 -0
  38. package/dist/config.js +5 -2
  39. package/dist/cultivate-cli.js +16 -23
  40. package/dist/extension.js +41 -29
  41. package/dist/init-config.js +5 -0
  42. package/dist/loop-cli.js +19 -7
  43. package/dist/loop-tools.js +11 -5
  44. package/dist/module-report.js +5 -4
  45. package/dist/pi.js +76 -19
  46. package/dist/question-dialog.js +1 -0
  47. package/dist/regenerate/activity.js +21 -0
  48. package/dist/regenerate/list-arguments.js +121 -0
  49. package/dist/regenerate/loop.js +21 -8
  50. package/dist/regenerate/plain-palette.js +51 -36
  51. package/dist/regenerate/regenerate.js +4 -2
  52. package/dist/regenerate/self-check.js +13 -6
  53. package/dist/regenerate/side-panel.js +1 -1
  54. package/dist/regenerate/spec-map-view.js +9 -5
  55. package/dist/regenerate/test-scripts.js +54 -10
  56. package/dist/regenerate/unit-review.js +1 -1
  57. package/dist/regenerate-cli.js +11 -5
  58. package/dist/release-cli.js +3 -8
  59. package/dist/review/live.js +2 -1
  60. package/dist/run-report.js +39 -1
  61. package/dist/validate-cli.js +3 -8
  62. package/docs/README.md +5 -12
  63. package/docs/authoring-plain.md +37 -119
  64. package/docs/config.md +53 -178
  65. package/docs/deploy.md +21 -80
  66. package/docs/development.md +13 -41
  67. package/docs/headless.md +45 -121
  68. package/docs/images/how-turri-works.svg +29 -31
  69. package/docs/installation.md +13 -40
  70. package/docs/parser.md +15 -43
  71. package/docs/regenerating.md +229 -938
  72. package/docs/sandbox.md +38 -152
  73. package/docs/skills-and-gates.md +50 -171
  74. package/docs/theming.md +45 -46
  75. package/docs/using.md +69 -345
  76. package/genome/rules/bullet-continuation.md +5 -13
  77. package/genome/rules/concise-specs-examples.md +2 -4
  78. package/genome/rules/concise-specs-integration-examples.md +2 -6
  79. package/genome/rules/concise-specs.md +1 -2
  80. package/genome/rules/func-specs.md +1 -0
  81. package/genome/rules/integration-embedded-testing.md +1 -4
  82. package/genome/rules/integrations.md +1 -2
  83. package/genome/rules/project-model.md +14 -35
  84. package/genome/rules/stdlib.md +14 -49
  85. package/genome/rules/turri-regeneration.md +48 -0
  86. package/genome/skills/add-implementation-requirement/SKILL.md +1 -2
  87. package/genome/skills/analyze-if-func-spec-too-complex/SKILL.md +3 -8
  88. package/genome/skills/brood/SKILL.md +37 -127
  89. package/genome/skills/brood/references/intent.md +4 -12
  90. package/genome/skills/brood/references/product.md +24 -76
  91. package/genome/skills/brood/references/stack.md +7 -29
  92. package/genome/skills/brood/references/testing.md +11 -42
  93. package/genome/skills/bud/SKILL.md +32 -109
  94. package/genome/skills/bud/references/intent.md +4 -11
  95. package/genome/skills/implement-conformance-testing-script/SKILL.md +3 -6
  96. package/genome/skills/implement-unit-testing-script/SKILL.md +1 -2
  97. package/genome/skills/mature/SKILL.md +21 -69
  98. package/genome/skills/refactor-module/SKILL.md +24 -2
  99. package/genome/skills/spawn/SKILL.md +28 -113
  100. package/package.json +1 -1
package/docs/sandbox.md CHANGED
@@ -1,12 +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
4
- not given, the rest of the project, or anything else on the machine - and pi's `cwd` is only
5
- where relative paths start: an absolute path resolves to itself and `bash` can `cd` anywhere. So
6
- turri fences its agents: their file tools check every path, and every command they run on this
7
- machine passes a command check, runs in an environment of the module's own behind an OS sandbox
8
- where the OS has one, and is audited afterwards. The rules live in code that runs the same on every
9
- 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'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.
10
4
 
11
5
  ## What an agent can reach
12
6
 
@@ -20,197 +14,92 @@ Each agent's file tools reach its own module and only the parts its role needs:
20
14
  | Explorers | its module's `code/` | nothing |
21
15
  | Test review | its module's `tests/` and `code/` | nothing |
22
16
 
23
- Nothing else exists for them: not another module, not turri's scratch area `plain_modules/.turri` (a test run's
24
- whole output reaches them through `read_output`, not by path), not the module's lineage `.plaingit`, and nothing
25
- above `plain_modules`. The specs it is
26
- building from reach it only through the prompt the harness writes. Nor does any context file: pi puts
27
- every `AGENTS.md` / `CLAUDE.md` from an agent's folder up into its system prompt, and turri
28
- turns that off for every agent it starts - so the project's own `AGENTS.md`, or yours in
29
- `~/.pi/agent`, never reaches a rendering agent, a judge or a skill's agent. Nor do pi's skills, which
30
- pi would list for an agent to load: each agent turri starts has one job, and turri runs its
31
- own gated skills itself. The conversation you talk to still loads both, as pi does - but for the skills turri has
32
- 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 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)).
33
18
 
34
19
  ## Every agent knows its walls
35
20
 
36
- Each agent's system prompt says where its walls are, in words generated from its fence, so they say exactly what is
37
- enforced and are the same on every platform:
38
-
39
- - its own folder or folders, by absolute path, and what its file tools read and write there - a role that only reads
40
- is told it writes nothing, "not with your file tools, not with your shell"; the conformance agent that it reads the
41
- code under test but writes tests, not code; the code fixer that it reads the conformance tests but fixes the code;
42
- - that its shell runs on this machine, in its folder, with this machine's toolchains, a home folder and a temporary
43
- folder of its own (`$HOME`, `$TMPDIR`), the package caches and an open network, and that nothing else exists for
44
- it - not the specs, not the project's other folders or modules, not your home;
45
- - what happens on a reach outside, matter-of-fact: a command that names a path outside is refused before it runs,
46
- and anything a command changes outside is put back, and it is told;
47
- - that the project's dependencies are its own, through the build manifest, and installing anything else is not: what
48
- the machine lacks it reports with `report_environment_error`, naming what is missing, and goes no further with what
49
- depends on it (see [when the environment lacks something](regenerating.md#when-the-environment-lacks-something));
50
- - that it may stop only what it started, and that a port something else holds means another port; that `ps` may show
51
- nothing but what it started, so a server is checked with `kill -0 $!`, its log or a request;
21
+ Each agent's system prompt says where its walls are, in words generated from its fence, so they say exactly what is enforced and are the same on every platform:
22
+
23
+ - its own folder or folders, by absolute path, and what its file tools read and write there - a role that only reads is told it writes nothing, "not with your file tools, not with your shell"; the conformance agent that it reads the code under test but writes tests, not code; the code fixer that it reads the conformance tests but fixes the code;
24
+ - that its shell runs on this machine, in its folder, with this machine's toolchains, a home folder and a temporary folder of its own (`$HOME`, `$TMPDIR`), the package caches and an open network, and that nothing else exists for it - not the specs, not the project's other folders or modules, not your home;
25
+ - what happens on a reach outside, matter-of-fact: a command that names a path outside is refused before it runs, and anything a command changes outside is put back, and it is told;
26
+ - that the project's dependencies are its own, through the build manifest, and installing anything else is not: what the machine lacks it reports with `report_environment_error`, naming what is missing, and goes no further with what depends on it (see [when the environment lacks something](regenerating.md#when-the-environment-lacks-something));
27
+ - that it may stop only what it started, and that a port something else holds means another port; that `ps` may show nothing but what it started, so a server is checked with `kill -0 $!`, its log or a request;
52
28
  - that temporary files go in `$TMPDIR`, `/tmp` being outside its walls.
53
29
 
54
- Beside its walls, an agent that writes code or tests - the coding agent, the conformance agent, the code fixer, the
55
- repair agent - is told up front what it would otherwise go looking for (`src/regenerate/workspace.ts`): whether its folder is
56
- empty, with nothing above it to find, or holds code whose unit tests passed when it was committed; that it is not a git
57
- repository, and `my_diff` shows its work; the versions of the toolchains its module's implementation reqs name, probed
58
- once on this machine, or that one is missing; that the package caches and Maven's repository are set for it, and to
59
- leave `JAVA_TOOL_OPTIONS` set; and that the network is open.
30
+ Beside its walls, an agent that writes code or tests - the coding agent, the conformance agent, the code fixer, the repair agent - is told up front what it would otherwise go looking for (`src/regenerate/workspace.ts`): whether its folder is empty, with nothing above it to find, or holds code whose unit tests passed when it was committed; that it is not a git repository, and `my_diff` shows its work; the versions of the toolchains its module's implementation reqs name, probed once on this machine, or that one is missing; that the package caches and Maven's repository are set for it, and to leave `JAVA_TOOL_OPTIONS` set; and that the network is open.
60
31
 
61
- No other path is named - nothing into `plain_modules/.turri`. A test run or a command whose output was cut names the
62
- whole of it by an id - not a file in its folder, which `read` says if it is given one - and the agent reads it with
63
- `read_output`: not by a path into `plain_modules/.turri` or to a
64
- temporary file on this machine (see [long output](regenerating.md#the-tests-must-actually-pass)).
32
+ No other path is named - nothing into `plain_modules/.turri`. A test run or a command whose output was cut names the whole of it by an id - not a file in its folder, which `read` says if it is given one - and the agent reads it with `read_output`: not by a path into `plain_modules/.turri` or to a temporary file on this machine (see [long output](regenerating.md#the-tests-must-actually-pass)).
65
33
 
66
34
  ## File tools
67
35
 
68
- `src/regenerate/confine.ts` rebuilds `read`, `write`, `edit`, `ls`, `grep` and `find` from pi's own
69
- factories with a check in front of every path. The check runs twice: once on the argument the
70
- tool was called with, and once on the absolute path the tool resolved it to, because the two can
71
- differ. Symlinks are resolved before comparing, so a link cannot walk out, and a glob containing
72
- `..` is refused outright.
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.
73
37
 
74
- The record of what the code is built from, `code/.turri.json`, is the harness's: no agent reads or writes it. `read`
75
- refuses it, and `ls`, `grep` and `find` leave it out of what they list - as the command check, the OS sandbox and the
76
- audit keep it from commands.
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.
77
39
 
78
- Credentials are the person's: no agent writes a `.env` or a variant of one (`.env.local`, `.env.test`) - a
79
- template, `.env.example`, is fine. The file tools refuse it, the command check refuses a command that would
80
- write one, and what a command does to one anyway is put back by the audit, the agent told why. A made-up credential makes every later run fail without anyone seeing why; a missing one is the
81
- judge's to report (see [regenerating the code](regenerating.md)), the person's to provide.
40
+ Credentials are the person's: no agent writes a `.env` or a variant of one (`.env.local`, `.env.test`) - a template, `.env.example`, is fine. The file tools refuse it, the command check refuses a command that would write one, and what a command does to one anyway is put back by the audit, the agent told why. A made-up credential makes every later run fail without anyone seeing why; a missing one is the judge's to report (see [regenerating the code](regenerating.md)), the person's to provide.
82
41
 
83
42
  ## Commands on this machine
84
43
 
85
- `bash` is the case no argument check can cover - one string, and no way to read for certain where it will read or
86
- write. So every command an agent runs - its `bash`, a service it starts - goes through every wall at once
87
- (`src/regenerate/agent-shell.ts`, `agentShell`), from its role's fence, the same as its file tools':
44
+ `bash` is the case no argument check can cover - one string, and no way to read for certain where it will read or write. So every command an agent runs - its `bash`, a service it starts - goes through every wall at once (`src/regenerate/agent-shell.ts`, `agentShell`), from its role's fence, the same as its file tools':
88
45
 
89
46
  1. **the command check**, before it runs - refused with exit 126 and the reason, a line in the job's log;
90
- 2. **the module's own environment** - not yours: the path, the locale, the terminal, the user and the toolchains'
91
- variables pass, nothing else - no provider key, no token, no SSH agent; a home folder of the module's own in
92
- `plain_modules/.turri/home/<module>`, kept between pulses, a `TMPDIR`, the package caches in the project, git
93
- stopped at the module folder, and what `agent-env:` passes (see [credentials for the agents](config.md#credentials-for-the-agents)).
94
- Every JVM - a test JVM Maven or Gradle forks too - is given the module's `TMPDIR` and home through
95
- `JAVA_TOOL_OPTIONS`, since Java takes both from the system rather than from `TMPDIR` and `HOME`; each says so in a
96
- "Picked up JAVA_TOOL_OPTIONS" line on its stderr;
47
+ 2. **the module's own environment** - not yours: the path, the locale, the terminal, the user and the toolchains' variables pass, nothing else - no provider key, no token, no SSH agent; a home folder of the module's own in `plain_modules/.turri/home/<module>`, kept between pulses, a `TMPDIR`, the package caches in the project, git stopped at the module folder, and what `agent-env:` passes (see [credentials for the agents](config.md#credentials-for-the-agents)). Every JVM - a test JVM Maven or Gradle forks too - is given the module's `TMPDIR` and home through `JAVA_TOOL_OPTIONS`, since Java takes both from the system rather than from `TMPDIR` and `HOME`; each says so in a "Picked up JAVA_TOOL_OPTIONS" line on its stderr;
97
48
  3. **the OS sandbox**, where one is up - Seatbelt on macOS, bubblewrap on Linux and WSL2;
98
49
  4. **the audit**, after it, which puts back what it changed that its role may not, and tells the agent.
99
50
 
100
51
  The job's log says, for each agent with a shell, which walls are up - and where no OS sandbox starts, why.
101
52
 
102
- The command runs through the host runner (`src/regenerate/shell.ts`): `bash -c` from the agent's folder, in a process group of
103
- its own. No command can hang a build: every one has a time limit - 2 minutes, or `TURRI_COMMAND_TIMEOUT` seconds: the
104
- agent's own `bash` unless it gives a timeout (at most 30 minutes), its unit and conformance test runs, the harness's own
105
- runs. A command that runs out is killed, it and everything it started, and reports exit 124 with a note on what to do
106
- instead: a server, a watcher or a prompt waiting for input never ends on its own, and has to be started in the
107
- background or given its input. A job that is stopped ends the command it is waiting on at once, the same way.
53
+ The command runs through the host runner (`src/regenerate/shell.ts`): `bash -c` from the agent's folder, in a process group of its own. No command can hang a build: every one has a time limit - 2 minutes, or `TURRI_COMMAND_TIMEOUT` seconds: the agent's own `bash` unless it gives a timeout (at most 30 minutes), its unit and conformance test runs, the harness's own runs. A command that runs out is killed, it and everything it started, and reports exit 124 with a note on what to do instead: a server, a watcher or a prompt waiting for input never ends on its own, and has to be started in the background or given its input. A job that is stopped ends the command it is waiting on at once, the same way.
108
54
 
109
- A server a command leaves running in the background is its pulse's: it runs on until the pulse ends, and is killed
110
- then - a pulse running beside it keeps its own. Everything still running is killed when turri exits, ended by a signal
111
- too. A turri killed outright cannot do that itself, so one small watcher beside it does, within two seconds, from the
112
- groups it recorded in the temporary folder (`turri-groups/`); and groups a turri left that nothing killed are swept when
113
- turri next starts. On Windows there are no process groups: a command's process tree is killed with `taskkill /T /F`,
114
- best effort.
55
+ A server a command leaves running in the background is its pulse's: it runs on until the pulse ends, and is killed then - a pulse running beside it keeps its own. Everything still running is killed when turri exits, ended by a signal too. A turri killed outright cannot do that itself, so one small watcher beside it does, within two seconds, from the groups it recorded in the temporary folder (`turri-groups/`); and groups a turri left that nothing killed are swept when turri next starts. On Windows there are no process groups: a command's process tree is killed with `taskkill /T /F`, best effort.
115
56
 
116
- The project's testing scripts - their static checks included - are not agents' commands: they are
117
- yours, and run on this machine as you would run them, unfenced, in your own environment - see [the testing scripts](regenerating.md#the-testing-scripts). The agent gets their
118
- output, nothing more.
57
+ The project's testing scripts - their static checks included - are not agents' commands: they are yours, and run on this machine as you would run them, unfenced, in your own environment - see [the testing scripts](regenerating.md#the-testing-scripts). The agent gets their output, nothing more.
119
58
 
120
59
  ### The command check
121
60
 
122
- Before an agent's command runs, its text is checked (`src/regenerate/command-check.ts`, `checkCommand`), on every platform, and
123
- refused - not run - with the rule it broke and what to do instead, when it:
61
+ Before an agent's command runs, its text is checked (`src/regenerate/command-check.ts`, `checkCommand`), on every platform, and refused - not run - with the rule it broke and what to do instead, when it:
124
62
 
125
- - names a path outside the role's roots - absolute, a `..` that climbs out of the agent's folder (after the `cd`s
126
- before it), or inside a quoted string (`python3 -c "..."`). `~`, `$HOME` and `$TMPDIR` are the module's own and
127
- allowed, as are the package caches, the machine's system folders and the toolchains, and the devices
128
- (`/dev/null`). A path is checked when it names something on disk - under a top folder such as `/Users`, `/home`,
129
- `/etc` or `/tmp`, one of the fence's, or one that exists - so an API route in a curl or a JSON body is not one, and
130
- nor is `/` alone, a slash in code (`case '/':`), though writing to `/` is refused;
63
+ - names a path outside the role's roots - absolute, a `..` that climbs out of the agent's folder (after the `cd`s before it), or inside a quoted string (`python3 -c "..."`). `~`, `$HOME` and `$TMPDIR` are the module's own and allowed, as are the package caches, the machine's system folders and the toolchains, and the devices (`/dev/null`). A path is checked when it names something on disk - under a top folder such as `/Users`, `/home`, `/etc` or `/tmp`, one of the fence's, or one that exists - so an API route in a curl or a JSON body is not one, and nor is `/` alone, a slash in code (`case '/':`), though writing to `/` is refused;
131
64
  - names the code record, `.turri.json`;
132
- - writes a credential file (`.env` and its variants; a template is fine) - by redirect, `tee`, `cp`, `mv` or an edit
133
- in place;
134
- - installs software outside the project: `sudo`, the system's package managers installing (`brew`, `apt`, `dnf`,
135
- `apk`, `pacman`, `choco`, `winget`, ...), toolchain managers installing (`nvm`, `sdk`, `rustup`, `pyenv`, `asdf`,
136
- ...), global installs (`npm -g`, `yarn global`, `pip --user`, `pipx`, `gem install`), a script downloaded and run
137
- (`curl ... | sh`), an installer (`.pkg`, `.msi`, `.dmg`, `.run`). The project's own dependencies - `npm install`,
138
- pip into its venv, `mvn`, `cargo`, `go mod` - are building the code, and pass;
139
- - kills what its pulse did not start: `pkill`, `killall`, `taskkill`, `fuser -k`, or `kill` aimed at a port or name
140
- lookup. `kill $!` and `kill %1` pass.
141
-
142
- On Windows, drive letters, backslashes and any case are read as Windows has them. It knowingly misses a path built at
143
- run time (`$(...)`, a variable, a script that reads one from a file): the OS sandbox and the audit are for those.
65
+ - writes a credential file (`.env` and its variants; a template is fine) - by redirect, `tee`, `cp`, `mv` or an edit in place;
66
+ - installs software outside the project: `sudo`, the system's package managers installing (`brew`, `apt`, `dnf`, `apk`, `pacman`, `choco`, `winget`, ...), toolchain managers installing (`nvm`, `sdk`, `rustup`, `pyenv`, `asdf`, ...), global installs (`npm -g`, `yarn global`, `pip --user`, `pipx`, `gem install`), a script downloaded and run (`curl ... | sh`), an installer (`.pkg`, `.msi`, `.dmg`, `.run`). The project's own dependencies - `npm install`, pip into its venv, `mvn`, `cargo`, `go mod` - are building the code, and pass;
67
+ - kills what its pulse did not start: `pkill`, `killall`, `taskkill`, `fuser -k`, or `kill` aimed at a port or name lookup. `kill $!` and `kill %1` pass.
68
+
69
+ On Windows, drive letters, backslashes and any case are read as Windows has them. It knowingly misses a path built at run time (`$(...)`, a variable, a script that reads one from a file): the OS sandbox and the audit are for those.
144
70
 
145
71
  ### The OS sandbox
146
72
 
147
- Under the check, where the OS offers one, the command runs behind an OS sandbox (`src/regenerate/os-sandbox.ts`), driven
148
- directly from the role's fence: Seatbelt (`sandbox-exec`) on macOS, bubblewrap on Linux and WSL2. It is an extra wall,
149
- never where a rule lives - where neither starts (native Windows, WSL1, a container that does not allow user
150
- namespaces) the check and the audit hold alone. What a command behind it reaches:
151
-
152
- - reads: its role's roots, the module's home, `TMPDIR` and the package caches, and of the machine only the system's
153
- folders, every folder on the agent's `PATH` (the folder itself, not its parent: `~/.cargo/bin`, not `~/.cargo`) - but
154
- a version manager's install whole, a `bin` whose parent is a version (`…/mise/installs/node/24.16.0/bin`), as its
155
- tools are links into the install beside it (`npm` into `lib/node_modules`) - the folders the toolchain variables
156
- point at (`JAVA_HOME`, `RUSTUP_HOME`, `NVM_DIR`, mise's `MISE_DATA_DIR`, fnm's, rbenv's, the Pythons uv fetches, ...)
157
- and the project's `toolchains:` - nothing else of the person's home;
158
- - writes: its role's own roots - none for a role that only reads - and the module's home, `TMPDIR` and caches. On macOS
159
- the developer tools' own scratch files too (`xcrun`'s database, clang's module cache), which go where macOS says, not
160
- where `TMPDIR` does - and a folder of the module's own in macOS's per-user temporary folder
161
- (`DIRHELPER_USER_DIR_SUFFIX`), where its JVMs keep their attach sockets, so Mockito's inline mocks, `jcmd` and
162
- `jstack` attach to a JVM it started; the person's JVMs' sockets, beside it, stay out of reach;
163
- - never the code record; no Unix socket outside its folders (on macOS, the name service's excepted); signals and
164
- process listings only within its own sandbox (bubblewrap's PID namespace, Seatbelt's same-sandbox), so `ps` shows
165
- nothing else and `kill` reaches nothing else;
73
+ Under the check, where the OS offers one, the command runs behind an OS sandbox (`src/regenerate/os-sandbox.ts`), driven directly from the role's fence: Seatbelt (`sandbox-exec`) on macOS, bubblewrap on Linux and WSL2. It is an extra wall, never where a rule lives - where neither starts (native Windows, WSL1, a container that does not allow user namespaces) the check and the audit hold alone. What a command behind it reaches:
74
+
75
+ - reads: its role's roots, the module's home, `TMPDIR` and the package caches, and of the machine only the system's folders, every folder on the agent's `PATH` (the folder itself, not its parent: `~/.cargo/bin`, not `~/.cargo`) - but a version manager's install whole, a `bin` whose parent is a version (`…/mise/installs/node/24.16.0/bin`), as its tools are links into the install beside it (`npm` into `lib/node_modules`) - the folders the toolchain variables point at (`JAVA_HOME`, `RUSTUP_HOME`, `NVM_DIR`, mise's `MISE_DATA_DIR`, fnm's, rbenv's, the Pythons uv fetches, ...) and the project's `toolchains:` - nothing else of the person's home;
76
+ - writes: its role's own roots - none for a role that only reads - and the module's home, `TMPDIR` and caches. On macOS the developer tools' own scratch files too (`xcrun`'s database, clang's module cache), which go where macOS says, not where `TMPDIR` does - and a folder of the module's own in macOS's per-user temporary folder (`DIRHELPER_USER_DIR_SUFFIX`), where its JVMs keep their attach sockets, so Mockito's inline mocks, `jcmd` and `jstack` attach to a JVM it started; the person's JVMs' sockets, beside it, stay out of reach;
77
+ - never the code record; no Unix socket outside its folders (on macOS, the name service's excepted); signals and process listings only within its own sandbox (bubblewrap's PID namespace, Seatbelt's same-sandbox), so `ps` shows nothing else and `kill` reaches nothing else;
166
78
  - the network as this machine has it;
167
79
  - at most 4096 processes and files of at most 8 GiB, set with `ulimit` inside.
168
80
 
169
- `sandboxAvailable()` tries it once and says which wall is up, or why none starts and how to give it one: bubblewrap not
170
- installed, user namespaces off (Docker: a user that is not root, `--security-opt seccomp=unconfined`), AppArmor in the way (Docker:
171
- `--security-opt apparmor=unconfined`; Ubuntu 24.04: its user-namespace restriction), or Docker's masked `/proc` - for
172
- which bubblewrap falls back to binding the host's `/proc` rather than mounting its own.
81
+ `sandboxAvailable()` tries it once and says which wall is up, or why none starts and how to give it one: bubblewrap not installed, 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: its user-namespace restriction), or Docker's masked `/proc` - for which bubblewrap falls back to binding the host's `/proc` rather than mounting its own.
173
82
 
174
83
  ### The audit
175
84
 
176
- After every agent command, on every platform - a sandbox up or not - turri puts back what the command's role may not
177
- change inside the project (`src/regenerate/audit.ts`): the specs, `config*.yaml`, the testing scripts, every credential file
178
- (the agent's own folder included), the code record, the module's lineage `.plaingit` (its refs and settings), the other
179
- modules, the folders the role only reads, and the part of its own module it does not write - the coding agent's
180
- conformance tests. A changed file is put back, a new one removed, a deleted one brought back: from a copy kept before
181
- the command, or - another module - as its last commit has it, unless a pulse is at work in that module, whose writes are
182
- not this command's to undo (left, and logged). Nor is a change the conversation made while the command ran: the chat's
183
- `edit` and `write`, a spec diff it applied, or anything while a command of its own ran - the chat editing a spec beside a
184
- pulse is the person's change, not the agent's, so it is left and logged (`src/regenerate/conversation-writes.ts`). The agents
185
- of a pulse start without extensions, so nothing of theirs is marked so. The agent is told in its command's result what was put back; the job's
186
- log gets a line; you are not told, and the pulse goes on. It compares sizes and modification times, skipping
187
- dependencies and build output, and contents only where those differ - about 35 ms on a project of 3000 files. Outside
188
- the project nothing can be put back: that is the OS sandbox's, and on Windows an accepted gap.
85
+ After every agent command, on every platform - a sandbox up or not - turri puts back what the command's role may not change inside the project (`src/regenerate/audit.ts`): the specs, `config*.yaml`, the testing scripts, every credential file (the agent's own folder included), the code record, the module's lineage `.plaingit` (its refs and settings), the other modules, the folders the role only reads, and the part of its own module it does not write - the coding agent's conformance tests. A changed file is put back, a new one removed, a deleted one brought back: from a copy kept before the command, or - another module - as its last commit has it, unless a pulse is at work in that module, whose writes are not this command's to undo (left, and logged). Nor is a change the conversation made while the command ran: the chat's `edit` and `write`, a spec diff it applied, or anything while a command of its own ran - the chat editing a spec beside a pulse is the person's change, not the agent's, so it is left and logged (`src/regenerate/conversation-writes.ts`). The agents of a pulse start without extensions, so nothing of theirs is marked so. The agent is told in its command's result what was put back; the job's log gets a line; you are not told, and the pulse goes on. It compares sizes and modification times, skipping dependencies and build output, and contents only where those differ - about 35 ms on a project of 3000 files. Outside the project nothing can be put back: that is the OS sandbox's, and on Windows an accepted gap.
189
86
 
190
87
  ## The web
191
88
 
192
- Every rendering agent - render, spec change, conformance, regression repair - can also search the
193
- web and read a page, for a library's documentation or an error it has not seen:
89
+ Every rendering agent - render, spec change, conformance, regression repair - can also search the web and read a page, for a library's documentation or an error it has not seen:
194
90
 
195
91
  | Tool | What it does |
196
92
  | --- | --- |
197
93
  | `web_search` | asks DuckDuckGo (no key needed) and returns title, address and snippet per result |
198
94
  | `web_fetch` | reads one page and returns it as text - HTML without its markup, text, JSON or XML as it is; long pages come in parts |
199
95
 
200
- `web_fetch` reads only the public web: localhost, the private ranges and the cloud metadata address are
201
- refused, and so is every redirect that leads to one - what the code talks to on this machine, the agent
202
- reaches with its own commands.
96
+ `web_fetch` reads only the public web: localhost, the private ranges and the cloud metadata address are refused, and so is every redirect that leads to one - what the code talks to on this machine, the agent reaches with its own commands.
203
97
 
204
- DuckDuckGo is used without a key through its Lite page, which is not an official API: it rate-limits,
205
- and a search that is turned away tells the agent to wait or fetch a page it knows instead.
98
+ DuckDuckGo is used without a key through its Lite page, which is not an official API: it rate-limits, and a search that is turned away tells the agent to wait or fetch a page it knows instead.
206
99
 
207
100
  ## Outside the sandbox: deploy
208
101
 
209
- What `deploy` starts runs on your machine unfenced: you asked to try the software, with your toolchains,
210
- in your browser. It runs from `dist/<module>`, a copy of the last
211
- commit outside `plain_modules`, so no rendering agent reaches it. Nothing is installed there without
212
- your say-so - every command that would install or download is put to you first, and the chat's shell
213
- refuses the rest. See [deploy](deploy.md).
102
+ What `deploy` starts runs on your machine unfenced: you asked to try the software, with your toolchains, in your browser. It runs from `dist/<module>`, a copy of the last commit outside `plain_modules`, so no rendering agent reaches it. Nothing is installed there without your say-so - every command that would install or download is put to you first, and the chat's shell refuses the rest. See [deploy](deploy.md).
214
103
 
215
104
  ## What turri keeps beside the code
216
105
 
@@ -227,7 +116,4 @@ It lives in `plain_modules/.turri`, which ignores itself in git:
227
116
 
228
117
  ## What the machine lacks
229
118
 
230
- The agents use this machine's toolchains, and install nothing on it: not a toolchain, a system package, a driver or a
231
- global package. The project's own dependencies are theirs, through its package manager, into the project and its
232
- caches. What the machine lacks is yours to install: an agent that finds something missing reports it, the pulse stops,
233
- and you are told what to install - see [when the environment lacks something](regenerating.md#when-the-environment-lacks-something).
119
+ The agents use this machine's toolchains, and install nothing on it: not a toolchain, a system package, a driver or a global package. The project's own dependencies are theirs, through its package manager, into the project and its caches. What the machine lacks is yours to install: an agent that finds something missing reports it, the pulse stops, and you are told what to install - see [when the environment lacks something](regenerating.md#when-the-environment-lacks-something).
@@ -1,13 +1,8 @@
1
1
  # Skills and gates
2
2
 
3
- A skill's review step is normally a paragraph asking the agent to check its own work. Agents talk
4
- their way past paragraphs. turri makes the review an edge in a graph instead: the harness
5
- decides when a gate runs, spawns the judges itself, and branches on a structured verdict. The
6
- model's only job is producing the artifact under review.
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.
7
4
 
8
- pi supplies the agent runtime (sessions, tools, multi-provider models); LangGraph supplies the
9
- control flow. The gate syntax lives in `SKILL.md`, so a gated skill still loads in any
10
- spec-compliant harness - it just runs ungated there.
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.
11
6
 
12
7
  ## The syntax
13
8
 
@@ -40,189 +35,73 @@ metadata:
40
35
  ...
41
36
  ```
42
37
 
43
- A skill declares its steps with a `## Steps` section whose `###` headings are the steps; without
44
- it the whole skill is one step, which is what most skills are. Steps are never inferred from `###`
45
- headings elsewhere in the body - prose skills are full of those. Each gate becomes a conditional
46
- edge after its step; a single-step skill is gated by naming the skill itself. None of the skills turri ships
47
- declares a gate today - `brood`'s phases end on the person's confirmation alone - but a project's own skill can.
48
-
49
- `paced: false` under `metadata:` lets a skill write spec changes one after another on its own:
50
- `write_spec_diff` otherwise refuses a new change until the user has answered a question since the
51
- last one - one answer, one change, as an interview writes them. `spawn` is unpaced: the tool tells it so, and after
52
- each change to write the next at once, not to carry on with an interview it does not have.
53
-
54
- `questioning:` under `metadata:` names the steps the questioning level caps (`src/questioning.ts`) - `brood`'s
55
- `[stack, product]`, `bud`'s `[product]`. When there are any and someone to ask, the harness itself asks the first question of the first
56
- step: how many questions those steps may ask - light (3), medium (5), thorough (10) or best judgement - and tells the
57
- agent the answer beside each step's instructions, with how broad each question is to be: broad at light, a whole area
58
- at once, narrow at thorough, an edge case at a time. Every level covers the same ground, in less or more depth. In a
59
- capped step `ask_user` refuses a question past the cap; the step's closing confirmation, asked with
60
- `confirms_phase: true`, is never counted. `end_questions` ends the step's questions sooner - best judgement's only
61
- end. What the questions leave open, the agent decides itself and says what it chose. The level, and how many
62
- questions each step has asked, are kept in the run's `run.json`, so a resumed run keeps them. A step it does not name,
63
- or a run with nobody to ask, is not capped.
64
-
65
- `spec-diff-per-answer:` under `metadata:` names the steps where every answer is a spec change - `brood`'s
66
- and `bud`'s `[product]`, at every questioning level. There `ask_user` refuses the next question until the last answer is written
67
- with `write_spec_diff` (a confirmation, or a question dismissed, owes nothing), and `write_spec_diff` no longer waits
68
- for an answer before a change: the agent writes as many as the step needs, those it decides itself included. The
69
- level limits the questions, never the spec changes.
70
-
71
- A step ends when its agent's turn does. A turn that ends on a question in the reply would end the step there, half
72
- done, on a question no one reads - the person sees only what `ask_user` asks. So such a step is sent back, twice at
73
- most: to ask with `ask_user` if the question is truly the person's, or, in an unpaced skill, to decide it itself -
74
- then to carry on until the step is done.
75
-
76
- `phases:` under `metadata:` says what each step does, in a short line a step by its name - for the person, not
77
- the agent: while the skill runs, the line above the input shows its phases, and under them the one it is in with
78
- its line (`groundwork - the language, frameworks, storage and architecture`). A phase's name alone
79
- says little to someone who has not read the skill. Every skill with more than one step describes each; a line for a
80
- step the skill does not have is an error, as a gate after one is.
81
-
82
- A skill gets the tools its `allowed-tools` names, and nothing else: one that names none has none. So
83
- every skill here names its own - the file tools (`read`, `write`, `edit`, `bash`, `ls`, `grep`, `find`)
84
- for the ones that write specs, resources or scripts, only the reading ones for those that analyse
85
- and report, and turri's own (`write_spec_diff` and the ones below) where a skill uses them.
86
-
87
- What a run changed is part of its result: every file of the project it added, changed or removed -
88
- its specs, resources, scripts; not the built code - or that it changed none, which is said as such,
89
- so a run that wrote nothing is never reported as done. A skill without gates says it was not
90
- reviewed. A `.plain` file a skill edited with the file tools is not rendered by it: the result says
91
- so, for the conversation to render the change.
92
-
93
- A skill acts on the work loop with the conversation's own tools, on the conversation's loop - never
94
- copies of them (`src/loop-tools.ts`). Every skill with the loop has `drift`: what is left to regenerate, what
95
- is regenerating, and "Fully regenerated" once nothing is. One that lists `regenerate` and `halt` in `allowed-tools` -
96
- as `mature` does - can regenerate what a module's spec holds and halt a pulse, with why. `mature` lists two
97
- tools of its own as well: `watch_jobs` waits for its pulses, coming back as soon as one struggles -
98
- sent back four times or more, five setbacks, a spec conflict, a failure run again, over half an hour -
99
- with what it went through; `spec_findings` reads back, numbered and each once, what the pulses recorded
100
- since the run started: the assumptions and pitfalls in every codebase's working notes, a failed pulse's
101
- diagnosis, a spec that contradicts itself.
102
-
103
- Each step is told where its skill's files are, so a path it names - `references/intent.md` - and a
104
- skill it says to follow - `implement-unit-testing-script` - are found without searching.
38
+ A skill declares its steps with a `## Steps` section whose `###` headings are the steps; without it the whole skill is one step, which is what most skills are. Steps are never inferred from `###` headings elsewhere in the body - prose skills are full of those. Each gate becomes a conditional edge after its step; a single-step skill is gated by naming the skill itself. None of the skills turri ships declares a gate today - `brood`'s phases end on the person's confirmation alone - but a project's own skill can.
39
+
40
+ `paced: false` under `metadata:` lets a skill write spec changes one after another on its own: `write_spec_diff` otherwise refuses a new change until the user has answered a question since the last one - one answer, one change, as an interview writes them. `spawn` is unpaced: the tool tells it so, and after each change to write the next at once, not to carry on with an interview it does not have.
41
+
42
+ `questioning:` under `metadata:` names the steps the questioning level caps (`src/questioning.ts`) - `brood`'s `[stack, product]`, `bud`'s `[product]`. When there are any and someone to ask, the harness itself asks the first question of the first step: how many questions those steps may ask - light (3), medium (5), thorough (10) or best judgement - and tells the agent the answer beside each step's instructions, with how broad each question is to be: broad at light, a whole area at once, narrow at thorough, an edge case at a time. Every level covers the same ground, in less or more depth. In a capped step `ask_user` refuses a question past the cap; the step's closing confirmation, asked with `confirms_phase: true`, is never counted. `end_questions` ends the step's questions sooner - best judgement's only end. What the questions leave open, the agent decides itself and says what it chose. The level, and how many questions each step has asked, are kept in the run's `run.json`, so a resumed run keeps them. A step it does not name, or a run with nobody to ask, is not capped.
43
+
44
+ `spec-diff-per-answer:` under `metadata:` names the steps where every answer is a spec change - `brood`'s and `bud`'s `[product]`, at every questioning level. There `ask_user` refuses the next question until the last answer is written with `write_spec_diff` (a confirmation, or a question dismissed, owes nothing), and `write_spec_diff` no longer waits for an answer before a change: the agent writes as many as the step needs, those it decides itself included. The level limits the questions, never the spec changes.
45
+
46
+ A step ends when its agent's turn does. A turn that ends on a question in the reply would end the step there, half done, on a question no one reads - the person sees only what `ask_user` asks. So such a step is sent back, twice at most: to ask with `ask_user` if the question is truly the person's, or, in an unpaced skill, to decide it itself - then to carry on until the step is done.
47
+
48
+ `phases:` under `metadata:` says what each step does, in a short line a step by its name - for the person, not the agent: while the skill runs, the line above the input shows its phases, and under them the one it is in with its line (`groundwork - the language, frameworks, storage and architecture`). A phase's name alone says little to someone who has not read the skill. Every skill with more than one step describes each; a line for a step the skill does not have is an error, as a gate after one is.
49
+
50
+ A skill gets the tools its `allowed-tools` names, and nothing else: one that names none has none. So every skill here names its own - the file tools (`read`, `write`, `edit`, `bash`, `ls`, `grep`, `find`) for the ones that write specs, resources or scripts, only the reading ones for those that analyse and report, and turri's own (`write_spec_diff` and the ones below) where a skill uses them.
51
+
52
+ What a run changed is part of its result: every file of the project it added, changed or removed - its specs, resources, scripts; not the built code - or that it changed none, which is said as such, so a run that wrote nothing is never reported as done. A skill without gates says it was not reviewed. A `.plain` file a skill edited with the file tools is not rendered by it: the result says so, for the conversation to render the change.
53
+
54
+ A skill acts on the work loop with the conversation's own tools, on the conversation's loop - never copies of them (`src/loop-tools.ts`). Every skill with the loop has `drift`: what is left to regenerate, what is regenerating, and "Fully regenerated" once nothing is. One that lists `regenerate` and `halt` in `allowed-tools` - as `mature` does - can regenerate what a module's spec holds and halt a pulse, with why. `mature` lists two tools of its own as well: `watch_jobs` waits for its pulses, coming back as soon as one struggles - sent back four times or more, five setbacks, a spec conflict, a failure run again, over half an hour - with what it went through; `spec_findings` reads back, numbered and each once, what the pulses recorded since the run started: the assumptions and pitfalls in every codebase's working notes, a failed pulse's diagnosis, a spec that contradicts itself.
55
+
56
+ Each step is told where its skill's files are, so a path it names - `references/intent.md` - and a skill it says to follow - `implement-unit-testing-script` - are found without searching.
105
57
 
106
58
  ## What the harness enforces, not the model
107
59
 
108
- - **Whether review happens.** The gate is an edge. There is no prompt the executor can write that
109
- skips it.
110
- - **How many attempts.** `max_retries` is a counter in graph state. The executor is never asked how
111
- many tries it would like.
112
- - **What "fail" means.** Judges answer through a schema (`submit_verdict`), so the router branches
113
- on a value. A judge that returns no verdict counts as a fail - silence is not consent.
114
- - **What gets reviewed.** The executor's only write path for the artifact is `write_artifact`,
115
- pointed at a path the graph chose. A step that produces no artifact fails its gate without
116
- spending a single judge call.
117
- - **Who is judging.** Each judge is a fresh session given the artifact and the rubric and nothing
118
- else. It never sees the executor's transcript, because a judge that inherits the actor's
119
- reasoning inherits its rationalisations.
120
-
121
- `on_fail: escalate` (and `retry` once retries are exhausted) pauses the graph on a LangGraph
122
- `interrupt`: in the TUI that is a real confirm dialog; with no UI there is nobody to ask, so the
123
- gate stands and the run stops.
124
-
125
- `judge_tools` gives a gate's judges read-only tools, for a rubric that checks what is on disk.
126
- Judges get nothing by default, which is right when the artifact is the whole subject.
127
-
128
- A gate can also have a `continuous` reviewer: a read-only agent that re-reads the work after every
129
- change the step makes and puts its findings on the executor's next tool result.
60
+ - **Whether review happens.** The gate is an edge. There is no prompt the executor can write that skips it.
61
+ - **How many attempts.** `max_retries` is a counter in graph state. The executor is never asked how many tries it would like.
62
+ - **What "fail" means.** Judges answer through a schema (`submit_verdict`), so the router branches on a value. A judge that returns no verdict counts as a fail - silence is not consent.
63
+ - **What gets reviewed.** The executor's only write path for the artifact is `write_artifact`, pointed at a path the graph chose. A step that produces no artifact fails its gate without spending a single judge call.
64
+ - **Who is judging.** Each judge is a fresh session given the artifact and the rubric and nothing else. It never sees the executor's transcript, because a judge that inherits the actor's reasoning inherits its rationalisations.
65
+
66
+ `on_fail: escalate` (and `retry` once retries are exhausted) pauses the graph on a LangGraph `interrupt`: in the TUI that is a real confirm dialog; with no UI there is nobody to ask, so the gate stands and the run stops.
67
+
68
+ `judge_tools` gives a gate's judges read-only tools, for a rubric that checks what is on disk. Judges get nothing by default, which is right when the artifact is the whole subject.
69
+
70
+ A gate can also have a `continuous` reviewer: a read-only agent that re-reads the work after every change the step makes and puts its findings on the executor's next tool result.
130
71
 
131
72
  ## What a step can do
132
73
 
133
- - **Interview the user.** `ask_user` puts one question to the person. In the TUI it is a dialog;
134
- from the CLI the options are numbered. Offering choices is required, not optional - a call with
135
- neither `options` nor `free_form: true` is refused - and every list carries "Something else…".
136
- `multi_select: true` keeps offering what is left until they are done. The skill's agent talks in a
137
- session of its own that the person never sees, so what the person must read to answer - the brief
138
- to confirm, the plan to approve - it passes as `ask_user`'s `context`, shown above the question in
139
- the dialog; `brood`, `spawn` and `bud` pass their briefs so. The agents are told to keep it to what can be taken in at
140
- a glance, about fifteen lines; one longer scrolls in a window of its own above the question (`src/question-dialog.ts`) -
141
- PgUp/PgDn, Shift+Up/Down or the mouse wheel - so the question and its options always show below it. pi's own select,
142
- 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:
143
- the last thing it said before asking - its account of a probe, a regeneration - is kept with the answer,
144
- in the run's trace and the transcript, never in the dialog.
145
-
146
- Beside "Something else…", every list an agent's question shows carries "Chat about this": the person says what they
147
- 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
148
- answer. A skill's agent answers in the `context` of the question asked again, since nothing else it writes reaches
149
- them; the conversation's agent answers in its reply and ends its turn, the person going on in the chat. It is not
150
- counted against the questioning level, and owes no spec change. The harness's own first question - how many the
151
- phases may ask - has no agent to answer, and does not offer it. Either choice is backed out of with Esc, or by typing
152
- nothing: the options come back, so one picked by accident costs nothing. Esc on the options themselves dismisses the
153
- question.
154
- - **Be asked something on the side.** A skill runs inside one `cultivate` call, so the conversation can answer
155
- nothing until it returns. `/btw <question>` (`btw.ts`) can: pi runs a command at once, even mid-turn, and it starts
156
- an agent of its own - fresh, on the chat's model, reading the project and turri and changing nothing - shown the
157
- conversation, the running skill's trace and the end of its agent's session, and the pulses. It is told it is turri,
158
- as the conversation's agent is (`identity.ts`), and answers as turri, in the first person - never as an onlooker
159
- who sends the person to turri with a message to pass on. Its answer goes in the
160
- transcript as an entry, in no model's context: the conversation and the skill go on as if nothing was asked. While
161
- a question of the skill's has the keys, `/btw <question>` typed as the answer (under "Something else…") is the
162
- same: answered, and the question asked again with the answer above it - the skill's agent never sees it.
163
- - **Build while it interviews.** `write_spec_diff` (`brood`, `spawn`) writes a change to the
164
- spec and queues its render in one call; the interview carries on while the builds run in the
165
- background, one after another. `drift` shows how they stand. The person sees its calls as
166
- `mutate`: a tool can be shown by a name of its own (`SHOWN_AS` in `regenerate/activity.ts`), while its
167
- agent calls it by the one that says what it takes.
168
- - **Regenerate, and conform.** A skill that names them in `allowed-tools` gets the conversation's own
169
- `regenerate`, `drift`, `halt` and `conform`, on its loop: `conform` schedules a conformance pulse for functional specs not built
170
- yet, as it does from the chat. The skill never waits for them.
171
- - **Choose the render model**, when the user says so - `engine`, the same tool the
172
- conversation has.
173
-
174
- The harness still owns the sequence. A step chooses what to ask; it does not choose whether the
175
- gate afterwards runs. A run is saved as it goes, so an interrupted one can be resumed where it
176
- stopped.
177
-
178
- While a skill runs in the chat, `cultivate`'s output leads with what its agent is doing now: the skill,
179
- its step and how long each has taken, its last few tool calls, and the end of what it is saying,
180
- refreshed every few seconds. pi shows only a running tool's first lines, and a step can take many
181
- minutes without a new line in the trace. The result the conversation gets back keeps the trace
182
- alone.
74
+ - **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.
75
+
76
+ 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.
77
+ - **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.
78
+ - **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.
79
+ - **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.
80
+ - **Choose the render model**, when the user says so - `engine`, the same tool the conversation has.
81
+
82
+ 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.
83
+
84
+ 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.
183
85
 
184
86
  ## A project's own skills
185
87
 
186
- A project can have skills of its own, in the same format: a folder a skill, with its `SKILL.md` - steps, gates,
187
- rubrics in its `references/` - in `.turri/skills/` or `.agents/skills/` of the project's folder (`src/skills.ts`).
188
- turri finds them as it starts, beside its own, and `cultivate` lists each as the project's, with where it is; `turri
189
- cultivate <name>` runs one by name, as it does turri's own. They are run as turri's own are: the harness owns their
190
- steps and gates, and their agents get the tools their frontmatter names.
88
+ A project can have skills of its own, in the same format: a folder a skill, with its `SKILL.md` - steps, gates, rubrics in its `references/` - in `.turri/skills/` or `.agents/skills/` of the project's folder (`src/skills.ts`). turri finds them as it starts, beside its own, and `cultivate` lists each as the project's, with where it is; `turri cultivate <name>` runs one by name, as it does turri's own. They are run as turri's own are: the harness owns their steps and gates, and their agents get the tools their frontmatter names.
191
89
 
192
- A name is one skill. turri's own come first, then `.turri/skills/`, then `.agents/skills/`; a skill whose name is taken
193
- already is left out, and the list says so - a project's `brood` never quietly stands in for turri's. A
194
- skill that does not parse is said to have failed to load, by its folder.
90
+ 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.
195
91
 
196
- `.turri/` is turri's scratch folder and ignores itself, all but `skills/`: its `.gitignore` is `*`, `!skills/`,
197
- `!skills/**`, and one written before is brought up to date. pi reads `.agents/skills/` too, as skills of its own the
198
- chat could follow by hand - the chat is told to run any `cultivate` lists with `cultivate`, so its gates still run.
92
+ `.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.
199
93
 
200
- The skills pi offers the chat leave out what turri has already (`src/chat-skills.ts`): a skill of a name turri runs
201
- itself - its own, or the project's - and any skill about \*\*\*plain, its specs or their testing scripts. pi gathers
202
- skills from `~/.pi/agent/skills`, `~/.agents/skills` and the `.agents/skills` of every folder from where turri started
203
- up to its git root, so a folder above the project can hold another tool's \*\*\*plain skills - a `load-plain-reference`
204
- the chat would load at the start of every session, though the whole \*\*\*plain reference, turri's
205
- [rules](#rules), is in its system prompt already. The person is told once which were left out. A skill about anything
206
- else is offered as pi offers it.
94
+ 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.
207
95
 
208
- A skill is found once, as turri starts: one added while it runs is there the next time. A project's skill agent runs
209
- 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
210
- project.
96
+ 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.
211
97
 
212
98
  ## Rules
213
99
 
214
- `genome/rules/` holds the \*\*\*plain authoring rules. Every rule, in full, is appended to the system
215
- prompt of the conversation and of the agents that write specs - never of the ones that write code.
216
- The block is byte-identical every time, so it sits inside the cached prefix - but it is ~40k
217
- tokens. The trade is deliberate: a rule the model has to decide to go and read is a rule it can
218
- skip. `src/rules.ts` is where to change it.
100
+ `genome/rules/` holds the \*\*\*plain authoring rules. Every rule, in full, is appended to the system prompt of the conversation and of the agents that write specs - never of the ones that write code. The block is byte-identical every time, so it sits inside the cached prefix - but it is ~40k tokens. The trade is deliberate: a rule the model has to decide to go and read is a rule it can skip. `src/rules.ts` is where to change it.
219
101
 
220
102
  ## Trying the gate semantics
221
103
 
222
- The gate machinery - the no-artifact rejection, the retry loop, escalation - is verified by live
223
- runs rather than automated tests. To poke at it, give a skill a gate like the one in [the syntax](#the-syntax) - a
224
- project's own, in `.turri/skills/`, with a rubric in its `references/` - run it with `turri cultivate <name>`, then
225
- change the gate:
104
+ The gate machinery - the no-artifact rejection, the retry loop, escalation - is verified by live runs rather than automated tests. To poke at it, give a skill a gate like the one in [the syntax](#the-syntax) - a project's own, in `.turri/skills/`, with a rubric in its `references/` - run it with `turri cultivate <name>`, then change the gate:
226
105
 
227
106
  | change | expected |
228
107
  | --- | --- |