@turri-dev/cli 0.1.4-next.5a94e68 → 0.1.4-next.67734c0
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 +39 -127
- package/dist/extension.js +11 -17
- package/dist/regenerate/side-panel.js +1 -1
- package/docs/README.md +5 -12
- package/docs/authoring-plain.md +37 -119
- package/docs/config.md +50 -176
- package/docs/deploy.md +21 -80
- package/docs/development.md +13 -41
- package/docs/headless.md +30 -136
- package/docs/installation.md +13 -40
- package/docs/parser.md +15 -43
- package/docs/regenerating.md +231 -964
- package/docs/sandbox.md +38 -152
- package/docs/skills-and-gates.md +50 -173
- package/docs/theming.md +45 -46
- package/docs/using.md +66 -356
- package/genome/rules/bullet-continuation.md +5 -13
- package/genome/rules/concise-specs-examples.md +2 -4
- package/genome/rules/concise-specs-integration-examples.md +2 -6
- package/genome/rules/concise-specs.md +1 -2
- package/genome/rules/func-specs.md +1 -0
- package/genome/rules/integration-embedded-testing.md +1 -4
- package/genome/rules/integrations.md +1 -2
- package/genome/rules/project-model.md +14 -35
- package/genome/rules/stdlib.md +14 -49
- package/genome/rules/turri-regeneration.md +48 -0
- package/genome/skills/add-implementation-requirement/SKILL.md +1 -2
- package/genome/skills/analyze-if-func-spec-too-complex/SKILL.md +3 -8
- package/genome/skills/brood/SKILL.md +37 -127
- package/genome/skills/brood/references/intent.md +4 -12
- package/genome/skills/brood/references/product.md +24 -76
- package/genome/skills/brood/references/stack.md +7 -29
- package/genome/skills/brood/references/testing.md +11 -42
- package/genome/skills/bud/SKILL.md +32 -109
- package/genome/skills/bud/references/intent.md +4 -11
- package/genome/skills/implement-conformance-testing-script/SKILL.md +3 -6
- package/genome/skills/implement-unit-testing-script/SKILL.md +1 -2
- package/genome/skills/mature/SKILL.md +21 -69
- package/genome/skills/spawn/SKILL.md +28 -113
- 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
|
-
|
|
38
|
-
|
|
39
|
-
- its
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
- that
|
|
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
|
-
|
|
134
|
-
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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).
|
package/docs/skills-and-gates.md
CHANGED
|
@@ -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,191 +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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
`
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
`
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
110
|
-
- **
|
|
111
|
-
|
|
112
|
-
- **
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
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:
|
|
145
|
-
the last thing it said before asking - its account of a probe, a regeneration - is kept with the answer,
|
|
146
|
-
in the run's trace and the transcript, never in the dialog.
|
|
147
|
-
|
|
148
|
-
Beside "Something else…", every list an agent's question shows carries "Chat about this": the person says what they
|
|
149
|
-
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
|
|
150
|
-
answer. A skill's agent answers in the `context` of the question asked again, since nothing else it writes reaches
|
|
151
|
-
them; the conversation's agent answers in its reply and ends its turn, the person going on in the chat. It is not
|
|
152
|
-
counted against the questioning level, and owes no spec change. The harness's own first question - how many the
|
|
153
|
-
phases may ask - has no agent to answer, and does not offer it. Either choice is backed out of with Esc, or by typing
|
|
154
|
-
nothing: the options come back, so one picked by accident costs nothing. Esc on the options themselves dismisses the
|
|
155
|
-
question.
|
|
156
|
-
- **Be asked something on the side.** A skill runs inside one `cultivate` call, so the conversation can answer
|
|
157
|
-
nothing until it returns. `/btw <question>` (`btw.ts`) can: pi runs a command at once, even mid-turn, and it starts
|
|
158
|
-
an agent of its own - fresh, on the chat's model, reading the project and turri and changing nothing - shown the
|
|
159
|
-
conversation, the running skill's trace and the end of its agent's session, and the pulses. It is told it is turri,
|
|
160
|
-
as the conversation's agent is (`identity.ts`), and answers as turri, in the first person - never as an onlooker
|
|
161
|
-
who sends the person to turri with a message to pass on. Its answer goes in the
|
|
162
|
-
transcript as an entry, in no model's context: the conversation and the skill go on as if nothing was asked. While
|
|
163
|
-
a question of the skill's has the keys, `/btw <question>` typed as the answer (under "Something else…") is the
|
|
164
|
-
same: answered, and the question asked again with the answer above it - the skill's agent never sees it.
|
|
165
|
-
- **Build while it interviews.** `write_spec_diff` (`brood`, `spawn`) writes a change to the
|
|
166
|
-
spec and queues its render in one call; the interview carries on while the builds run in the
|
|
167
|
-
background, one after another. `drift` shows how they stand. The person sees its calls as
|
|
168
|
-
`mutate`: a tool can be shown by a name of its own (`SHOWN_AS` in `regenerate/activity.ts`), while its
|
|
169
|
-
agent calls it by the one that says what it takes.
|
|
170
|
-
- **Regenerate, and conform.** A skill that names them in `allowed-tools` gets the conversation's own
|
|
171
|
-
`regenerate`, `drift`, `halt` and `conform`, on its loop: `conform` schedules a conformance pulse for functional specs not built
|
|
172
|
-
yet, as it does from the chat. The skill never waits for them.
|
|
173
|
-
- **Choose the render model**, when the user says so - `engine`, the same tool the
|
|
174
|
-
conversation has.
|
|
175
|
-
|
|
176
|
-
The harness still owns the sequence. A step chooses what to ask; it does not choose whether the
|
|
177
|
-
gate afterwards runs. A run is saved as it goes, so an interrupted one can be resumed where it
|
|
178
|
-
stopped.
|
|
179
|
-
|
|
180
|
-
While a skill runs in the chat, `cultivate`'s output leads with what its agent is doing now: the skill,
|
|
181
|
-
its step and how long each has taken, its last few tool calls, and the end of what it is saying,
|
|
182
|
-
refreshed every few seconds. pi shows only a running tool's first lines, and a step can take many
|
|
183
|
-
minutes without a new line in the trace. The result the conversation gets back keeps the trace
|
|
184
|
-
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.
|
|
185
85
|
|
|
186
86
|
## A project's own skills
|
|
187
87
|
|
|
188
|
-
A project can have skills of its own, in the same format: a folder a skill, with its `SKILL.md` - steps, gates,
|
|
189
|
-
rubrics in its `references/` - in `.turri/skills/` or `.agents/skills/` of the project's folder (`src/skills.ts`).
|
|
190
|
-
turri finds them as it starts, beside its own, and `cultivate` lists each as the project's, with where it is; `turri
|
|
191
|
-
cultivate <name>` runs one by name, as it does turri's own. They are run as turri's own are: the harness owns their
|
|
192
|
-
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.
|
|
193
89
|
|
|
194
|
-
A name is one skill. turri's own come first, then `.turri/skills/`, then `.agents/skills/`; a skill whose name is taken
|
|
195
|
-
already is left out, and the list says so - a project's `brood` never quietly stands in for turri's. A
|
|
196
|
-
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.
|
|
197
91
|
|
|
198
|
-
`.turri/` is turri's scratch folder and ignores itself, all but `skills/`: its `.gitignore` is `*`, `!skills/`,
|
|
199
|
-
`!skills/**`, and one written before is brought up to date. pi reads `.agents/skills/` too, as skills of its own the
|
|
200
|
-
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.
|
|
201
93
|
|
|
202
|
-
The skills pi offers the chat leave out what turri has already (`src/chat-skills.ts`): a skill of a name turri runs
|
|
203
|
-
itself - its own, or the project's - and any skill about \*\*\*plain, its specs or their testing scripts. pi gathers
|
|
204
|
-
skills from `~/.pi/agent/skills`, `~/.agents/skills` and the `.agents/skills` of every folder from where turri started
|
|
205
|
-
up to its git root, so a folder above the project can hold another tool's \*\*\*plain skills - a `load-plain-reference`
|
|
206
|
-
the chat would load at the start of every session, though the whole \*\*\*plain reference, turri's
|
|
207
|
-
[rules](#rules), is in its system prompt already. The person is told once which were left out. A skill about anything
|
|
208
|
-
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.
|
|
209
95
|
|
|
210
|
-
A skill is found once, as turri starts: one added while it runs is there the next time. A project's skill agent runs
|
|
211
|
-
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
|
|
212
|
-
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.
|
|
213
97
|
|
|
214
98
|
## Rules
|
|
215
99
|
|
|
216
|
-
`genome/rules/` holds the \*\*\*plain authoring rules. Every rule, in full, is appended to the system
|
|
217
|
-
prompt of the conversation and of the agents that write specs - never of the ones that write code.
|
|
218
|
-
The block is byte-identical every time, so it sits inside the cached prefix - but it is ~40k
|
|
219
|
-
tokens. The trade is deliberate: a rule the model has to decide to go and read is a rule it can
|
|
220
|
-
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.
|
|
221
101
|
|
|
222
102
|
## Trying the gate semantics
|
|
223
103
|
|
|
224
|
-
The gate machinery - the no-artifact rejection, the retry loop, escalation - is verified by live
|
|
225
|
-
runs rather than automated tests. To poke at it, give a skill a gate like the one in [the syntax](#the-syntax) - a
|
|
226
|
-
project's own, in `.turri/skills/`, with a rubric in its `references/` - run it with `turri cultivate <name>`, then
|
|
227
|
-
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:
|
|
228
105
|
|
|
229
106
|
| change | expected |
|
|
230
107
|
| --- | --- |
|