@turri-dev/cli 0.0.0-stage → 0.1.1
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/.pi/themes/turri-dark.json +80 -0
- package/.pi/themes/turri-light.json +82 -0
- package/LICENSE +21 -0
- package/README.md +272 -2
- package/assets/jellyfish/dark-00.png +0 -0
- package/assets/jellyfish/dark-01.png +0 -0
- package/assets/jellyfish/dark-02.png +0 -0
- package/assets/jellyfish/dark-03.png +0 -0
- package/assets/jellyfish/dark-04.png +0 -0
- package/assets/jellyfish/dark-05.png +0 -0
- package/assets/jellyfish/dark-06.png +0 -0
- package/assets/jellyfish/dark-07.png +0 -0
- package/assets/jellyfish/dark-08.png +0 -0
- package/assets/jellyfish/dark-09.png +0 -0
- package/assets/jellyfish/dark-10.png +0 -0
- package/assets/jellyfish/dark-11.png +0 -0
- package/assets/jellyfish/dark-12.png +0 -0
- package/assets/jellyfish/dark-13.png +0 -0
- package/assets/jellyfish/dark-14.png +0 -0
- package/assets/jellyfish/fallback.txt +252 -0
- package/assets/jellyfish/light-00.png +0 -0
- package/assets/jellyfish/light-01.png +0 -0
- package/assets/jellyfish/light-02.png +0 -0
- package/assets/jellyfish/light-03.png +0 -0
- package/assets/jellyfish/light-04.png +0 -0
- package/assets/jellyfish/light-05.png +0 -0
- package/assets/jellyfish/light-06.png +0 -0
- package/assets/jellyfish/light-07.png +0 -0
- package/assets/jellyfish/light-08.png +0 -0
- package/assets/jellyfish/light-09.png +0 -0
- package/assets/jellyfish/light-10.png +0 -0
- package/assets/jellyfish/light-11.png +0 -0
- package/assets/jellyfish/light-12.png +0 -0
- package/assets/jellyfish/light-13.png +0 -0
- package/assets/jellyfish/light-14.png +0 -0
- package/bin/turri.mjs +29 -0
- package/dist/ask.js +149 -0
- package/dist/brand.js +50 -0
- package/dist/btw.js +174 -0
- package/dist/chat-models.js +37 -0
- package/dist/chat-skills.js +18 -0
- package/dist/cli.js +178 -0
- package/dist/config-command.js +197 -0
- package/dist/config-edit.js +367 -0
- package/dist/config.js +489 -0
- package/dist/conform-cli.js +29 -0
- package/dist/cultivate-cli.js +110 -0
- package/dist/drift-cli.js +25 -0
- package/dist/environment.js +70 -0
- package/dist/extension.js +2372 -0
- package/dist/genome.js +301 -0
- package/dist/graph.js +246 -0
- package/dist/identity.js +35 -0
- package/dist/init-config.js +511 -0
- package/dist/judge.js +62 -0
- package/dist/lineage-cli.js +13 -0
- package/dist/loop-cli.js +132 -0
- package/dist/loop-tools.js +225 -0
- package/dist/mature-tools.js +193 -0
- package/dist/model-check.js +117 -0
- package/dist/module-report.js +9 -0
- package/dist/pi-launch.js +31 -0
- package/dist/pi.js +813 -0
- package/dist/plain/check.js +76 -0
- package/dist/plain/concepts.js +213 -0
- package/dist/plain/errors.js +43 -0
- package/dist/plain/files.js +57 -0
- package/dist/plain/highlight.js +195 -0
- package/dist/plain/index.js +15 -0
- package/dist/plain/liquid.js +129 -0
- package/dist/plain/markdown.js +208 -0
- package/dist/plain/parser.js +752 -0
- package/dist/plain/plain.tmLanguage.json +124 -0
- package/dist/plain/spec.js +291 -0
- package/dist/plain/stdlib.js +44 -0
- package/dist/question-dialog.js +125 -0
- package/dist/questioning.js +109 -0
- package/dist/quiet.js +125 -0
- package/dist/regenerate/activity.js +187 -0
- package/dist/regenerate/agent-env.js +190 -0
- package/dist/regenerate/agent-shell.js +114 -0
- package/dist/regenerate/arbiter.js +133 -0
- package/dist/regenerate/audit.js +203 -0
- package/dist/regenerate/cases.js +83 -0
- package/dist/regenerate/change-view.js +102 -0
- package/dist/regenerate/choose-conformance.js +117 -0
- package/dist/regenerate/chunk-plan.js +625 -0
- package/dist/regenerate/codebase.js +75 -0
- package/dist/regenerate/command-check.js +312 -0
- package/dist/regenerate/compaction.js +20 -0
- package/dist/regenerate/confine.js +310 -0
- package/dist/regenerate/conform.js +133 -0
- package/dist/regenerate/conformance-agents.js +955 -0
- package/dist/regenerate/conformance.js +936 -0
- package/dist/regenerate/conversation-writes.js +100 -0
- package/dist/regenerate/credentials.js +88 -0
- package/dist/regenerate/debug-log.js +228 -0
- package/dist/regenerate/debugger.js +90 -0
- package/dist/regenerate/delivery.js +19 -0
- package/dist/regenerate/diagnose-job.js +40 -0
- package/dist/regenerate/diagnose.js +81 -0
- package/dist/regenerate/diff-view.js +240 -0
- package/dist/regenerate/drift.js +194 -0
- package/dist/regenerate/engine.js +118 -0
- package/dist/regenerate/environment-error.js +220 -0
- package/dist/regenerate/job-log.js +102 -0
- package/dist/regenerate/job-panel.js +336 -0
- package/dist/regenerate/jobs-tree.js +300 -0
- package/dist/regenerate/jobs.js +427 -0
- package/dist/regenerate/lanes.js +32 -0
- package/dist/regenerate/lineage.js +384 -0
- package/dist/regenerate/loop.js +1435 -0
- package/dist/regenerate/masking.js +95 -0
- package/dist/regenerate/notes.js +397 -0
- package/dist/regenerate/os-sandbox.js +243 -0
- package/dist/regenerate/paths.js +165 -0
- package/dist/regenerate/plain-palette.js +83 -0
- package/dist/regenerate/prompt.js +704 -0
- package/dist/regenerate/records.js +294 -0
- package/dist/regenerate/regenerate.js +598 -0
- package/dist/regenerate/regression.js +181 -0
- package/dist/regenerate/release.js +262 -0
- package/dist/regenerate/requires.js +241 -0
- package/dist/regenerate/resources.js +164 -0
- package/dist/regenerate/self-check.js +111 -0
- package/dist/regenerate/services.js +155 -0
- package/dist/regenerate/shell.js +319 -0
- package/dist/regenerate/side-panel.js +278 -0
- package/dist/regenerate/spec-map-view.js +574 -0
- package/dist/regenerate/spec-map.js +561 -0
- package/dist/regenerate/spec-steps.js +527 -0
- package/dist/regenerate/state.js +10 -0
- package/dist/regenerate/steer.js +91 -0
- package/dist/regenerate/steering.js +119 -0
- package/dist/regenerate/stop-report.js +161 -0
- package/dist/regenerate/submit-work.js +71 -0
- package/dist/regenerate/test-kinds.js +124 -0
- package/dist/regenerate/test-scripts.js +695 -0
- package/dist/regenerate/testing.js +406 -0
- package/dist/regenerate/transcript-view.js +1 -0
- package/dist/regenerate/unit-gate.js +20 -0
- package/dist/regenerate/unit-review.js +347 -0
- package/dist/regenerate/web.js +256 -0
- package/dist/regenerate/workspace.js +99 -0
- package/dist/regenerate-cli.js +68 -0
- package/dist/release-agent.js +114 -0
- package/dist/release-cli.js +131 -0
- package/dist/release-tools.js +148 -0
- package/dist/review/live.js +165 -0
- package/dist/rules.js +47 -0
- package/dist/self-invocation.js +110 -0
- package/dist/setup.js +245 -0
- package/dist/skill-progress.js +78 -0
- package/dist/skill.js +149 -0
- package/dist/skills.js +74 -0
- package/dist/spec-diff-cli.js +37 -0
- package/dist/spec-tools.js +155 -0
- package/dist/stack.js +158 -0
- package/dist/stacks.js +97 -0
- package/dist/stdlib.js +100 -0
- package/dist/terminal-images.js +46 -0
- package/dist/tools.js +242 -0
- package/dist/validate-cli.js +52 -0
- package/docs/README.md +58 -0
- package/docs/authoring-plain.md +245 -0
- package/docs/config.md +353 -0
- package/docs/deploy.md +109 -0
- package/docs/development.md +75 -0
- package/docs/headless.md +148 -0
- package/docs/images/how-turri-works.svg +50 -0
- package/docs/images/turri-banner.png +0 -0
- package/docs/installation.md +118 -0
- package/docs/parser.md +108 -0
- package/docs/regenerating.md +1116 -0
- package/docs/sandbox.md +233 -0
- package/docs/skills-and-gates.md +254 -0
- package/docs/theming.md +76 -0
- package/docs/using.md +454 -0
- package/genome/rules/bullet-continuation.md +69 -0
- package/genome/rules/concise-specs-examples.md +431 -0
- package/genome/rules/concise-specs-integration-examples.md +270 -0
- package/genome/rules/concise-specs.md +80 -0
- package/genome/rules/definitions.md +107 -0
- package/genome/rules/examples/integration-embedded-testing/run_conformance_tests_java.sh +163 -0
- package/genome/rules/examples/integration-embedded-testing/run_unittests_java.sh +51 -0
- package/genome/rules/exported-concepts.md +39 -0
- package/genome/rules/func-specs.md +129 -0
- package/genome/rules/impl-reqs.md +68 -0
- package/genome/rules/import-modules.md +48 -0
- package/genome/rules/integration-embedded-testing.md +276 -0
- package/genome/rules/integration-embedded.md +171 -0
- package/genome/rules/integration-standalone.md +135 -0
- package/genome/rules/integrations.md +137 -0
- package/genome/rules/linked-resources.md +69 -0
- package/genome/rules/module-structure.md +105 -0
- package/genome/rules/project-model.md +88 -0
- package/genome/rules/required-concepts.md +44 -0
- package/genome/rules/requires-modules.md +80 -0
- package/genome/rules/stdlib.md +73 -0
- package/genome/rules/test-reqs.md +57 -0
- package/genome/skills/add-acceptance-test/SKILL.md +100 -0
- package/genome/skills/add-concept/SKILL.md +71 -0
- package/genome/skills/add-functional-spec/SKILL.md +84 -0
- package/genome/skills/add-functional-specs/SKILL.md +118 -0
- package/genome/skills/add-implementation-requirement/SKILL.md +86 -0
- package/genome/skills/add-resource/SKILL.md +105 -0
- package/genome/skills/add-template/SKILL.md +64 -0
- package/genome/skills/add-test-requirement/SKILL.md +73 -0
- package/genome/skills/analyze-2-func-specs/SKILL.md +101 -0
- package/genome/skills/analyze-func-specs/SKILL.md +123 -0
- package/genome/skills/analyze-if-func-spec-too-complex/SKILL.md +211 -0
- package/genome/skills/break-down-func-spec/SKILL.md +205 -0
- package/genome/skills/brood/SKILL.md +229 -0
- package/genome/skills/brood/references/intent.md +35 -0
- package/genome/skills/brood/references/product.md +101 -0
- package/genome/skills/brood/references/stack.md +61 -0
- package/genome/skills/brood/references/testing.md +56 -0
- package/genome/skills/bud/SKILL.md +148 -0
- package/genome/skills/bud/references/checklist.md +44 -0
- package/genome/skills/bud/references/intent.md +30 -0
- package/genome/skills/check-plain-env/SKILL.md +285 -0
- package/genome/skills/consolidate-concepts/SKILL.md +192 -0
- package/genome/skills/create-import-module/SKILL.md +104 -0
- package/genome/skills/create-requires-module/SKILL.md +109 -0
- package/genome/skills/debug-specs/SKILL.md +188 -0
- package/genome/skills/implement-conformance-testing-script/SKILL.md +238 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_cypress.ps1 +337 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_golang.ps1 +111 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_java.sh +136 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.ps1 +103 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.sh +150 -0
- package/genome/skills/implement-unit-testing-script/SKILL.md +160 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_flutter.ps1 +87 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_golang.ps1 +73 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_java.sh +72 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_python.ps1 +81 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_python.sh +98 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_react.ps1 +87 -0
- package/genome/skills/init-plain-project/SKILL.md +117 -0
- package/genome/skills/init-plain-project/references/checklist.md +30 -0
- package/genome/skills/mature/SKILL.md +126 -0
- package/genome/skills/refactor-module/SKILL.md +196 -0
- package/genome/skills/resolve-section-ownership/SKILL.md +64 -0
- package/genome/skills/resolve-spec-conflict/SKILL.md +96 -0
- package/genome/skills/spawn/SKILL.md +163 -0
- package/package.json +68 -4
- package/stdlib/backend/common.plain +25 -0
- package/stdlib/backend/conformance/common.plain +12 -0
- package/stdlib/backend/conformance/go-gin.plain +9 -0
- package/stdlib/backend/conformance/go-nethttp.plain +9 -0
- package/stdlib/backend/conformance/java-springboot.plain +9 -0
- package/stdlib/backend/conformance/python-django.plain +9 -0
- package/stdlib/backend/conformance/python-fastapi.plain +9 -0
- package/stdlib/backend/conformance/python-flask.plain +9 -0
- package/stdlib/backend/conformance/rust-axum.plain +9 -0
- package/stdlib/backend/conformance/typescript-express.plain +9 -0
- package/stdlib/backend/conformance/typescript-fastify.plain +9 -0
- package/stdlib/backend/conformance/typescript-nestjs.plain +9 -0
- package/stdlib/backend/go-gin.plain +18 -0
- package/stdlib/backend/go-nethttp.plain +18 -0
- package/stdlib/backend/java-springboot.plain +19 -0
- package/stdlib/backend/python-django.plain +19 -0
- package/stdlib/backend/python-fastapi.plain +23 -0
- package/stdlib/backend/python-flask.plain +23 -0
- package/stdlib/backend/rust-axum.plain +17 -0
- package/stdlib/backend/typescript-express.plain +19 -0
- package/stdlib/backend/typescript-fastify.plain +19 -0
- package/stdlib/backend/typescript-nestjs.plain +19 -0
- package/stdlib/cli/common.plain +17 -0
- package/stdlib/cli/conformance/common.plain +11 -0
- package/stdlib/cli/conformance/go-cobra.plain +9 -0
- package/stdlib/cli/conformance/go-flag.plain +9 -0
- package/stdlib/cli/conformance/java-picocli.plain +9 -0
- package/stdlib/cli/conformance/python-argparse.plain +9 -0
- package/stdlib/cli/conformance/python-click.plain +9 -0
- package/stdlib/cli/conformance/python-typer.plain +9 -0
- package/stdlib/cli/conformance/rust-clap.plain +9 -0
- package/stdlib/cli/conformance/typescript-commander.plain +9 -0
- package/stdlib/cli/go-cobra.plain +14 -0
- package/stdlib/cli/go-flag.plain +14 -0
- package/stdlib/cli/java-picocli.plain +15 -0
- package/stdlib/cli/python-argparse.plain +18 -0
- package/stdlib/cli/python-click.plain +16 -0
- package/stdlib/cli/python-typer.plain +16 -0
- package/stdlib/cli/rust-clap.plain +14 -0
- package/stdlib/cli/typescript-commander.plain +17 -0
- package/stdlib/frontend/angular.plain +17 -0
- package/stdlib/frontend/common.plain +17 -0
- package/stdlib/frontend/conformance/angular.plain +5 -0
- package/stdlib/frontend/conformance/common.plain +11 -0
- package/stdlib/frontend/conformance/react-vite.plain +5 -0
- package/stdlib/frontend/conformance/sveltekit-spa.plain +5 -0
- package/stdlib/frontend/conformance/vue-vite.plain +5 -0
- package/stdlib/frontend/react-vite.plain +16 -0
- package/stdlib/frontend/sveltekit-spa.plain +16 -0
- package/stdlib/frontend/vue-vite.plain +16 -0
- package/stdlib/lang/go.plain +13 -0
- package/stdlib/lang/java.plain +12 -0
- package/stdlib/lang/python.plain +18 -0
- package/stdlib/lang/rust.plain +12 -0
- package/stdlib/lang/typescript.plain +13 -0
- package/stdlib/library/common.plain +15 -0
- package/stdlib/library/conformance/common.plain +9 -0
- package/stdlib/library/conformance/go.plain +9 -0
- package/stdlib/library/conformance/java-gradle.plain +9 -0
- package/stdlib/library/conformance/java-maven.plain +9 -0
- package/stdlib/library/conformance/python.plain +9 -0
- package/stdlib/library/conformance/rust.plain +9 -0
- package/stdlib/library/conformance/typescript.plain +9 -0
- package/stdlib/library/go.plain +12 -0
- package/stdlib/library/java-gradle.plain +13 -0
- package/stdlib/library/java-maven.plain +13 -0
- package/stdlib/library/python.plain +13 -0
- package/stdlib/library/rust.plain +12 -0
- package/stdlib/library/typescript.plain +15 -0
package/docs/sandbox.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# The sandbox
|
|
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.
|
|
10
|
+
|
|
11
|
+
## What an agent can reach
|
|
12
|
+
|
|
13
|
+
Each agent's file tools reach its own module and only the parts its role needs:
|
|
14
|
+
|
|
15
|
+
| Role | Reads | Writes |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| Coding agent, regression repair | its module's `code/` | its module's `code/` |
|
|
18
|
+
| Conformance agent | its module's `tests/` and `code/` | its module's `tests/` |
|
|
19
|
+
| Code fixer | its module's `code/` and `tests/` | its module's `code/` |
|
|
20
|
+
| Explorers | its module's `code/` | nothing |
|
|
21
|
+
| Test review | its module's `tests/` and `code/` | nothing |
|
|
22
|
+
|
|
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)).
|
|
33
|
+
|
|
34
|
+
## Every agent knows its walls
|
|
35
|
+
|
|
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;
|
|
52
|
+
- that temporary files go in `$TMPDIR`, `/tmp` being outside its walls.
|
|
53
|
+
|
|
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.
|
|
60
|
+
|
|
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)).
|
|
65
|
+
|
|
66
|
+
## File tools
|
|
67
|
+
|
|
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.
|
|
73
|
+
|
|
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.
|
|
77
|
+
|
|
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.
|
|
82
|
+
|
|
83
|
+
## Commands on this machine
|
|
84
|
+
|
|
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':
|
|
88
|
+
|
|
89
|
+
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;
|
|
97
|
+
3. **the OS sandbox**, where one is up - Seatbelt on macOS, bubblewrap on Linux and WSL2;
|
|
98
|
+
4. **the audit**, after it, which puts back what it changed that its role may not, and tells the agent.
|
|
99
|
+
|
|
100
|
+
The job's log says, for each agent with a shell, which walls are up - and where no OS sandbox starts, why.
|
|
101
|
+
|
|
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.
|
|
108
|
+
|
|
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.
|
|
115
|
+
|
|
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.
|
|
119
|
+
|
|
120
|
+
### The command check
|
|
121
|
+
|
|
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:
|
|
124
|
+
|
|
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;
|
|
131
|
+
- 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.
|
|
144
|
+
|
|
145
|
+
### The OS sandbox
|
|
146
|
+
|
|
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;
|
|
166
|
+
- the network as this machine has it;
|
|
167
|
+
- at most 4096 processes and files of at most 8 GiB, set with `ulimit` inside.
|
|
168
|
+
|
|
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.
|
|
173
|
+
|
|
174
|
+
### The audit
|
|
175
|
+
|
|
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.
|
|
189
|
+
|
|
190
|
+
## The web
|
|
191
|
+
|
|
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:
|
|
194
|
+
|
|
195
|
+
| Tool | What it does |
|
|
196
|
+
| --- | --- |
|
|
197
|
+
| `web_search` | asks DuckDuckGo (no key needed) and returns title, address and snippet per result |
|
|
198
|
+
| `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
|
+
|
|
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.
|
|
203
|
+
|
|
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.
|
|
206
|
+
|
|
207
|
+
## Outside the sandbox: deploy
|
|
208
|
+
|
|
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).
|
|
214
|
+
|
|
215
|
+
## What turri keeps beside the code
|
|
216
|
+
|
|
217
|
+
It lives in `plain_modules/.turri`, which ignores itself in git:
|
|
218
|
+
|
|
219
|
+
| | |
|
|
220
|
+
| --- | --- |
|
|
221
|
+
| `cache/` | package caches - pip, npm, cargo, Maven, Gradle, Go - so a dependency downloads once per project |
|
|
222
|
+
| `home/<module>` | each module's home folder for its agents' commands, kept between pulses |
|
|
223
|
+
| `tmp/<module>` | each module's `TMPDIR` |
|
|
224
|
+
| `scripts/` | the throwaway copies test runs happen in, emptied after each run |
|
|
225
|
+
| `logs/` | whole outputs cut short for an agent, read through `read_output` |
|
|
226
|
+
| `services/` | what the services agents started printed |
|
|
227
|
+
|
|
228
|
+
## What the machine lacks
|
|
229
|
+
|
|
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).
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# Skills and gates
|
|
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.
|
|
7
|
+
|
|
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.
|
|
11
|
+
|
|
12
|
+
## The syntax
|
|
13
|
+
|
|
14
|
+
Under `metadata:`, which the Agent Skills spec reserves for arbitrary key-values:
|
|
15
|
+
|
|
16
|
+
```yaml
|
|
17
|
+
---
|
|
18
|
+
name: draft-spec
|
|
19
|
+
description: Drafts a ***plain spec, judged before it is kept.
|
|
20
|
+
allowed-tools: [read, write, edit, bash, ls, grep, find]
|
|
21
|
+
metadata:
|
|
22
|
+
gates:
|
|
23
|
+
- after: product # the ### step this gate guards
|
|
24
|
+
review:
|
|
25
|
+
rubric: ./references/rubric.md
|
|
26
|
+
judges: 2 # independent sessions, not n samples of one
|
|
27
|
+
model: anthropic/claude-sonnet-5 # optional; defaults to the executor's model
|
|
28
|
+
quorum: all # all | majority | any
|
|
29
|
+
on_fail: retry # retry | escalate | abort
|
|
30
|
+
max_retries: 2
|
|
31
|
+
judge_tools: [read, ls, grep, find] # read-only tools the rubric needs
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Steps
|
|
35
|
+
|
|
36
|
+
### product
|
|
37
|
+
...prose the executor reads...
|
|
38
|
+
|
|
39
|
+
### finalize
|
|
40
|
+
...
|
|
41
|
+
```
|
|
42
|
+
|
|
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]`. 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
|
+
`[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.
|
|
105
|
+
|
|
106
|
+
## What the harness enforces, not the model
|
|
107
|
+
|
|
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.
|
|
130
|
+
|
|
131
|
+
## What a step can do
|
|
132
|
+
|
|
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.
|
|
183
|
+
|
|
184
|
+
## A project's own skills
|
|
185
|
+
|
|
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.
|
|
191
|
+
|
|
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.
|
|
195
|
+
|
|
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.
|
|
199
|
+
|
|
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.
|
|
207
|
+
|
|
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.
|
|
211
|
+
|
|
212
|
+
## Rules
|
|
213
|
+
|
|
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.
|
|
219
|
+
|
|
220
|
+
## Trying the gate semantics
|
|
221
|
+
|
|
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:
|
|
226
|
+
|
|
227
|
+
| change | expected |
|
|
228
|
+
| --- | --- |
|
|
229
|
+
| `max_retries: 0` | the first failure escalates instead of retrying |
|
|
230
|
+
| `on_fail: abort` | the first failure ends the run, exit 1 |
|
|
231
|
+
| `quorum: any` | one passing judge is enough |
|
|
232
|
+
| `judges: 3` | three sessions run concurrently |
|
|
233
|
+
| `model: google/gemini-3-pro` | judges run on another provider; an unconfigured model falls back with a note |
|
|
234
|
+
| `after: drafts` | rejected at load: `gate references unknown step 'drafts'` |
|
|
235
|
+
|
|
236
|
+
A trace, with a rubric strict enough that the first draft fails:
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
> step draft attempt 1
|
|
240
|
+
| Expired cached tokens are now ignored and removed instead of being returned.
|
|
241
|
+
> gate draft 2 independent judges
|
|
242
|
+
judge 1: fail (2)
|
|
243
|
+
judge 2: fail (2)
|
|
244
|
+
retry <- gate draft
|
|
245
|
+
- [starts with Fixed:/Added:/Changed:] ...
|
|
246
|
+
- [names the source file that changed] ...
|
|
247
|
+
|
|
248
|
+
> step draft attempt 2
|
|
249
|
+
| Fixed: src/cache.ts now ignores and removes expired cached tokens instead of returning them.
|
|
250
|
+
> gate draft 2 independent judges
|
|
251
|
+
judge 1: pass
|
|
252
|
+
judge 2: pass
|
|
253
|
+
advance <- gate draft
|
|
254
|
+
```
|
package/docs/theming.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Theming
|
|
2
|
+
|
|
3
|
+
Two brand themes ship in `.pi/themes/`, built from the brand tokens. `turri` starts in
|
|
4
|
+
`turri-dark`, the dark one; `--use-theme` picks another for that run:
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
turri # turri-dark
|
|
8
|
+
turri --use-theme turri-light/turri-dark # follows terminal appearance
|
|
9
|
+
turri --use-theme dark # pi's own
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The second form follows your terminal's light/dark appearance. The default is for
|
|
13
|
+
`turri` only: pi's settings are left alone, so a plain `pi` keeps its theme. With the
|
|
14
|
+
themes copied to `~/.pi/agent/themes/` (`cp .pi/themes/*.json ~/.pi/agent/themes/`), `{"theme": "turri-dark"}` in
|
|
15
|
+
`~/.pi/agent/settings.json` gives a plain `pi` the same one.
|
|
16
|
+
|
|
17
|
+
Bad input fails quietly - an unknown `--use-theme` name and a malformed theme file both run without
|
|
18
|
+
complaint - so if the colours do not change, suspect a typo rather than the mechanism.
|
|
19
|
+
|
|
20
|
+
## Why there are two
|
|
21
|
+
|
|
22
|
+
Half the palette is unusable on the other background. Contrast against brand black and brand
|
|
23
|
+
white:
|
|
24
|
+
|
|
25
|
+
| token | on `#0f0f0f` | on `#ffffff` |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| brand-yellow | 17.07 | 1.12 |
|
|
28
|
+
| brand-green | 14.77 | 1.30 |
|
|
29
|
+
| brand-green-light | 13.34 | 1.44 |
|
|
30
|
+
| brand-blue | **1.97** | 9.72 |
|
|
31
|
+
| brand-green-dark | 1.56 | 12.31 |
|
|
32
|
+
| brand-red | 3.38 | 5.67 |
|
|
33
|
+
|
|
34
|
+
So `turri-dark` carries yellow, green and green-light, and `turri-light` carries blue and
|
|
35
|
+
green-dark. **Brand blue never appears as text in the dark theme** - at 1.97:1 on brand black it is
|
|
36
|
+
illegible - so the dark theme's accent is brand yellow, and blue appears only in the light theme
|
|
37
|
+
and in both themes' HTML-export backgrounds. The one place blue stands for something in both is the spec
|
|
38
|
+
map's `◆` - a functional spec its conformance tests pass for: brand blue in a light theme, and in a dark
|
|
39
|
+
one its tint 55% of the way to white, `#919aec`, 7.34:1 on brand black (`brandBlue` in
|
|
40
|
+
`src/regenerate/plain-palette.ts`).
|
|
41
|
+
|
|
42
|
+
`--color-brand-status-warning` on white is 3.49:1, under the 4.5:1 bar for body text, so it is used
|
|
43
|
+
only for accents in the light theme, never for prose.
|
|
44
|
+
|
|
45
|
+
## Backgrounds
|
|
46
|
+
|
|
47
|
+
The dark theme paints no panels: the backgrounds of your messages, turri's messages and the
|
|
48
|
+
tool rows are `""`, the terminal's own background, so the transcript reads as one surface whatever
|
|
49
|
+
the terminal's colour is - brand black or not, no block shows up as a box. Two are deliberately
|
|
50
|
+
painted, because they have to be visible to work:
|
|
51
|
+
|
|
52
|
+
| token | value | why |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `selectedBg` | `#223936` | a selection you cannot see is not a selection |
|
|
55
|
+
| `searchMatchBg` | `#e0ff6e` | search hits, with black text on top |
|
|
56
|
+
|
|
57
|
+
With tool rows carrying no background, their state is signalled by the title and the diff colours.
|
|
58
|
+
|
|
59
|
+
**The window's background is your terminal's setting**, and the transcript is drawn on it. For the
|
|
60
|
+
brand look, set it to brand black, `#0f0f0f`, in your terminal profile.
|
|
61
|
+
|
|
62
|
+
## Spec diffs
|
|
63
|
+
|
|
64
|
+
A spec diff in the panels is not painted with the theme's colours for code: those are made for code,
|
|
65
|
+
and the turri themes' backgrounds are all the surface colour, so a diff drawn with them would have no
|
|
66
|
+
fills and hardly any highlighting. It has a palette of its own (`src/regenerate/plain-palette.ts`), one for
|
|
67
|
+
a dark theme and one for a light, picked by the theme's appearance: a hue for each piece of \*\*\*plain -
|
|
68
|
+
a section's header, a concept, a link, text in quotes or backticks, a front matter key, a module,
|
|
69
|
+
a comment - at least 7:1 on the surface, and a fill behind each added and removed line, green and red,
|
|
70
|
+
that every colour still reads on.
|
|
71
|
+
|
|
72
|
+
## The font
|
|
73
|
+
|
|
74
|
+
`--font-brand` cannot be applied. pi draws into a terminal, so the typeface is your terminal
|
|
75
|
+
emulator's setting, and NeueMontreal is proportional - a terminal needs a monospaced face. Set it
|
|
76
|
+
in iTerm2 / Ghostty / Alacritty, not here.
|