@zenera/cli 1.1.21 → 1.1.23
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 +40 -3
- package/dist/archive.d.ts +139 -0
- package/dist/archive.js +376 -0
- package/dist/banner.d.ts +33 -24
- package/dist/banner.js +25 -6
- package/dist/commands/check.js +1 -0
- package/dist/commands/export.d.ts +3 -0
- package/dist/commands/export.js +255 -0
- package/dist/commands/import.d.ts +3 -0
- package/dist/commands/import.js +173 -0
- package/dist/commands/index.d.ts +5 -1
- package/dist/commands/index.js +13 -1
- package/dist/commands/init.js +1 -0
- package/dist/commands/inspect.js +1 -0
- package/dist/commands/key.js +1 -0
- package/dist/commands/list.js +1 -0
- package/dist/commands/memory.js +118 -10
- package/dist/commands/open.js +1 -0
- package/dist/commands/run.js +3 -0
- package/dist/commands/sandbox.js +1 -0
- package/dist/commands/version.js +1 -0
- package/dist/lib.d.ts +2 -1
- package/dist/lib.js +2 -1
- package/dist/meta.d.ts +1 -1
- package/dist/meta.js +5 -28
- package/dist/resolve.js +19 -12
- package/dist/sandbox.js +1 -0
- package/dist/scaffold.d.ts +8 -0
- package/dist/scaffold.js +7 -2
- package/dist/term.d.ts +2 -0
- package/dist/term.js +2 -0
- package/dist/tui/markdown.d.ts +16 -0
- package/dist/tui/markdown.js +259 -0
- package/dist/tui/theme.js +1 -1
- package/dist/tui/wrap.d.ts +1 -1
- package/dist/validate.js +150 -4
- package/package.json +12 -6
- package/templates/editor/.github/copilot-instructions.md +62 -33
- package/templates/editor/.github/prompts/spec-sync-project.prompt.md +56 -3
- package/templates/editor/.github/skills/zen-cli/SKILL.md +21 -16
- package/templates/editor/.github/skills/zen-cli/references/check.md +6 -0
- package/templates/editor/.github/skills/zen-cli/references/export.md +107 -0
- package/templates/editor/.github/skills/zen-cli/references/memory.md +51 -13
- package/templates/editor/.github/skills/zen-cli/references/projects.md +1 -0
- package/templates/editor/.github/skills/zen-cli/references/rag.md +43 -3
- package/templates/editor/.github/skills/zen-cli/references/sandbox.md +30 -2
- package/templates/editor/.github/skills/zen-instructions/SKILL.md +232 -0
- package/templates/editor/.github/skills/zen-memory/SKILL.md +2 -1
- package/templates/editor/.github/skills/zen-memory-warmup/SKILL.md +684 -0
- package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +56 -23
- package/templates/editor/.github/skills/zen-rag-schema/SKILL.md +55 -28
- package/templates/editor/.github/skills/zen-review/SKILL.md +17 -1
- package/templates/editor/.github/skills/zen-sandbox/SKILL.md +257 -0
- package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +16 -3
- package/templates/editor/.vscode/agents.schema.json +8 -3
- package/templates/project/SPECIFICATION.md.tmpl +4 -1
- package/templates/project/agents/files-instructions.md +86 -0
- package/templates/project/agents/fork-instructions.md +17 -0
- package/templates/project/agents/memory-instructions.md +37 -0
- package/templates/project/agents.yaml.tmpl +3 -3
- package/templates/project/gitignore +12 -1
- package/templates/project/scripts/_setup.sh +68 -11
|
@@ -19,17 +19,19 @@ hits for `limit` are not an answer; the eight lines around one of them are.
|
|
|
19
19
|
## The commands
|
|
20
20
|
|
|
21
21
|
```
|
|
22
|
-
zen rag docs <index|search|list|grep|show|stats> [path...]
|
|
22
|
+
zen rag docs <index|restore|ready|search|list|grep|show|stats> [path...]
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
| Command
|
|
26
|
-
|
|
|
27
|
-
| `index`
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
25
|
+
| Command | Answers | Embedder? | Typical |
|
|
26
|
+
| --------- | ------------------------------------------ | --------- | ------- |
|
|
27
|
+
| `index` | builds the thing | yes | minutes |
|
|
28
|
+
| `restore` | _put the vectors back_ | **yes** | minutes |
|
|
29
|
+
| `ready` | _can this index answer at all?_ | no | instant |
|
|
30
|
+
| `search` | _where does this corpus talk about X?_ | **yes** | seconds |
|
|
31
|
+
| `list` | _what documents/headings/tables are here?_ | no | instant |
|
|
32
|
+
| `grep` | _does the string X appear anywhere?_ | no | instant |
|
|
33
|
+
| `show` | _print this section, verbatim_ | no | instant |
|
|
34
|
+
| `stats` | _what is in this index?_ | no | instant |
|
|
33
35
|
|
|
34
36
|
Only `search` ranks, and only `search` costs a network round trip - it embeds
|
|
35
37
|
the query before it can compare anything. The other four read `outline.json`
|
|
@@ -80,6 +82,35 @@ the answer.)
|
|
|
80
82
|
`manifest.json` records **which embedder made the vectors**, so a search with a
|
|
81
83
|
different model is refused rather than answered with noise.
|
|
82
84
|
|
|
85
|
+
### What survives a clone
|
|
86
|
+
|
|
87
|
+
`lance/` is binary, large, and rebuildable, so a project commits the index and
|
|
88
|
+
git-ignores the vectors. What a fresh checkout holds is therefore an index that
|
|
89
|
+
**looks built and cannot answer**: the manifest is there, `sources/` is there,
|
|
90
|
+
and the first search fails deep in the store with `holds no searchable table`.
|
|
91
|
+
|
|
92
|
+
Two commands close that gap, and neither needs the original documents:
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
zen rag docs ready --dir assets/docs-db # exit 0 searchable, exit 3 not
|
|
96
|
+
zen rag docs restore --dir assets/docs-db # re-embed from sources/
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`ready` answers with its **exit code**, so a setup step branches on it without
|
|
100
|
+
parsing anything; `--json` gives the same answer as `{ ready, state, embedding }`
|
|
101
|
+
for a caller that wants the detail. `restore` reads the documents back out of
|
|
102
|
+
`sources/`, re-embeds them with the model the manifest names, and renames the
|
|
103
|
+
result into place - so a failed restore leaves the old index untouched, at the
|
|
104
|
+
cost of room for both while it runs.
|
|
105
|
+
|
|
106
|
+
> **Never test for a built index with `[ -f .../manifest.json ]`.** The manifest
|
|
107
|
+
> is committed and the vectors are not, so that test is true on exactly the
|
|
108
|
+
> machine where it is wrong. `zen rag docs ready` is the test.
|
|
109
|
+
|
|
110
|
+
`restore` is also how an index **changes embedder**: `--embedding <ref>`
|
|
111
|
+
re-embeds everything and rewrites the manifest, so later searches ask for the
|
|
112
|
+
new model. Same corpus, same names, same chunk ids - only the vectors are new.
|
|
113
|
+
|
|
83
114
|
### How a document is cut up
|
|
84
115
|
|
|
85
116
|
Chunking follows the markdown rather than a character count: a paragraph, a
|
|
@@ -582,17 +613,19 @@ sections down. Cite the document name and the line numbers.
|
|
|
582
613
|
|
|
583
614
|
## When it goes wrong
|
|
584
615
|
|
|
585
|
-
| Symptom | Cause
|
|
586
|
-
| ------------------------------------------------ |
|
|
587
|
-
| "not installed" | `@zenera/rag` is not resolvable - install it, or use the two-`--package` npx form
|
|
588
|
-
| `nothing to index` | No `.md`/`.markdown`/`.txt`/`.text` under those paths, or they are all hidden
|
|
589
|
-
| A document you expected is missing | Hidden directory, `node_modules`, an unlisted extension, or over 16 MB - check stderr
|
|
590
|
-
| Refused for a different embedding | The index records the model that built it;
|
|
591
|
-
| No manifest / not an index | A build that did not finish. `manifest.json` is written last on purpose
|
|
592
|
-
|
|
|
593
|
-
| `
|
|
594
|
-
| A
|
|
595
|
-
| `no
|
|
596
|
-
|
|
|
597
|
-
|
|
|
598
|
-
|
|
|
616
|
+
| Symptom | Cause |
|
|
617
|
+
| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
|
|
618
|
+
| "not installed" | `@zenera/rag` is not resolvable - install it, or use the two-`--package` npx form |
|
|
619
|
+
| `nothing to index` | No `.md`/`.markdown`/`.txt`/`.text` under those paths, or they are all hidden |
|
|
620
|
+
| A document you expected is missing | Hidden directory, `node_modules`, an unlisted extension, or over 16 MB - check stderr |
|
|
621
|
+
| Refused for a different embedding | The index records the model that built it; pass the right `--embedding`, or move it with `restore --embedding` |
|
|
622
|
+
| No manifest / not an index | A build that did not finish. `manifest.json` is written last on purpose |
|
|
623
|
+
| `holds no searchable table` | A manifest with no vectors - a clone, since `lance/` is git-ignored. `restore` it |
|
|
624
|
+
| A step reported `skipped` but nothing can search | The step tested for `manifest.json` instead of asking `ready` |
|
|
625
|
+
| A build is refused | `.lock` - another build is running. It is taken over when the process is gone |
|
|
626
|
+
| `provider "openai": no api key` | `zen key ls` - the keyring, or a real environment variable |
|
|
627
|
+
| A usage error before any credential is asked for | Deliberate: everything about the invocation is checked first, so a typo is a typo |
|
|
628
|
+
| `no document matched --file` | The pattern is matched against the document **name**; `list files` prints them |
|
|
629
|
+
| Nothing matched | Exit 0 with an empty answer. Try fewer words, or `--mode text` for an exact string |
|
|
630
|
+
| Answers quoting text that is no longer there | Nothing watches the documents. Re-index after they change |
|
|
631
|
+
| A search took ten seconds, using no CPU | One embedding round trip, not the index. `list`/`grep`/`show` make none |
|
|
@@ -18,18 +18,20 @@ word "password" appears in forty places that are not the one you want.
|
|
|
18
18
|
## The commands
|
|
19
19
|
|
|
20
20
|
```
|
|
21
|
-
zen rag schema <index|search|list|grep|trace|show|stats> [spec...]
|
|
21
|
+
zen rag schema <index|restore|ready|search|list|grep|trace|show|stats> [spec...]
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
| Command
|
|
25
|
-
|
|
|
26
|
-
| `index`
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
24
|
+
| Command | Answers | Embedder? | Typical |
|
|
25
|
+
| --------- | -------------------------------------- | --------- | ------- |
|
|
26
|
+
| `index` | builds the thing | yes | minutes |
|
|
27
|
+
| `restore` | _put the vectors back_ | **yes** | minutes |
|
|
28
|
+
| `ready` | _can this index answer at all?_ | no | instant |
|
|
29
|
+
| `search` | _what is this API's way to do X?_ | **yes** | seconds |
|
|
30
|
+
| `list` | _what methods/types/fields are there?_ | no | instant |
|
|
31
|
+
| `grep` | _does the string X appear anywhere?_ | no | instant |
|
|
32
|
+
| `trace` | _which call can reach this field?_ | no | instant |
|
|
33
|
+
| `show` | _print exactly these things_ | no | instant |
|
|
34
|
+
| `stats` | _what is in this index?_ | no | instant |
|
|
33
35
|
|
|
34
36
|
Only `search` ranks, and only `search` costs a network round trip - it embeds
|
|
35
37
|
the query before it can compare anything. The other five read `graph.json` off
|
|
@@ -108,6 +110,29 @@ schema-db/
|
|
|
108
110
|
`manifest.json` records **which embedder made the vectors**, so a search with a
|
|
109
111
|
different model is refused rather than answered with noise.
|
|
110
112
|
|
|
113
|
+
### What survives a clone
|
|
114
|
+
|
|
115
|
+
`lance/` is binary, large, and rebuildable, so a project commits the index and
|
|
116
|
+
git-ignores the vectors. What a fresh checkout holds is therefore an index that
|
|
117
|
+
**looks built and cannot answer**: the manifest is there, the graph is there,
|
|
118
|
+
and the first search fails with `holds no searchable table`.
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
zen rag schema ready --dir assets/schema-db # exit 0 searchable, exit 3 not
|
|
122
|
+
zen rag schema restore --dir assets/schema-db # re-embed from sources/
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`ready` answers with its **exit code**, so a setup step branches on it without
|
|
126
|
+
parsing anything. `restore` rebuilds from the bundled copies in `sources/` and
|
|
127
|
+
renames the result into place, so a failure leaves the old index untouched -
|
|
128
|
+
which means an index built with `--no-sources` cannot be restored at all, and
|
|
129
|
+
says so. `--embedding <ref>` is how an index moves to another model: everything
|
|
130
|
+
is re-embedded and the manifest rewritten.
|
|
131
|
+
|
|
132
|
+
> **Never test for a built index with `[ -f .../manifest.json ]`.** The manifest
|
|
133
|
+
> is committed and the vectors are not, so that test is true on exactly the
|
|
134
|
+
> machine where it is wrong. `zen rag schema ready` is the test.
|
|
135
|
+
|
|
111
136
|
## Installing
|
|
112
137
|
|
|
113
138
|
`zen rag` ships in `@zenera/rag`, a sibling of the CLI. It has no binary of its
|
|
@@ -134,13 +159,13 @@ have not installed globally.
|
|
|
134
159
|
zen rag schema index <spec...> [--embedding <ref>] [-o <dir>] [--batch <n>]
|
|
135
160
|
```
|
|
136
161
|
|
|
137
|
-
| Flag | Default | Meaning
|
|
138
|
-
| ------------------- | ------------- |
|
|
139
|
-
| `--embedding <ref>` | - | Which embedder makes the vectors. Omit it to see the choices
|
|
140
|
-
| `-o`, `--out <dir>` | `./schema-db` | Where the index goes; `$ZEN_SCHEMA_DB` if that is set
|
|
141
|
-
| `--batch <n>` | `96` | Texts per embedding request, and how often progress prints
|
|
142
|
-
| `--no-sources` | - | Keep no copy of the documents; `show --source` rebuilds them |
|
|
143
|
-
| `--quiet` | - | No narration
|
|
162
|
+
| Flag | Default | Meaning |
|
|
163
|
+
| ------------------- | ------------- | ---------------------------------------------------------------------------------------------- |
|
|
164
|
+
| `--embedding <ref>` | - | Which embedder makes the vectors. Omit it to see the choices |
|
|
165
|
+
| `-o`, `--out <dir>` | `./schema-db` | Where the index goes; `$ZEN_SCHEMA_DB` if that is set |
|
|
166
|
+
| `--batch <n>` | `96` | Texts per embedding request, and how often progress prints |
|
|
167
|
+
| `--no-sources` | - | Keep no copy of the documents; `show --source` rebuilds them, and `restore` becomes impossible |
|
|
168
|
+
| `--quiet` | - | No narration |
|
|
144
169
|
|
|
145
170
|
```sh
|
|
146
171
|
zen rag schema index openapi.yaml --embedding openai:text-embedding-3-small
|
|
@@ -647,14 +672,16 @@ Worked: the invoice total is `Invoice.amount_due` (minor units), returned by
|
|
|
647
672
|
|
|
648
673
|
## When it goes wrong
|
|
649
674
|
|
|
650
|
-
| Symptom | Cause
|
|
651
|
-
| ------------------------------------------------ |
|
|
652
|
-
| "not installed" | `@zenera/rag` is not resolvable - install it, or use the two-`--package` npx form
|
|
653
|
-
| Refused for a different embedding | The index records the model that built it;
|
|
654
|
-
| No manifest / not an index | A build that did not finish. `manifest.json` is written last on purpose
|
|
655
|
-
| `
|
|
656
|
-
| A
|
|
657
|
-
|
|
|
658
|
-
|
|
|
659
|
-
|
|
|
660
|
-
|
|
|
675
|
+
| Symptom | Cause |
|
|
676
|
+
| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
|
|
677
|
+
| "not installed" | `@zenera/rag` is not resolvable - install it, or use the two-`--package` npx form |
|
|
678
|
+
| Refused for a different embedding | The index records the model that built it; pass the right `--embedding`, or move it with `restore --embedding` |
|
|
679
|
+
| No manifest / not an index | A build that did not finish. `manifest.json` is written last on purpose |
|
|
680
|
+
| `holds no searchable table` | A manifest with no vectors - a clone, since `lance/` is git-ignored. `restore` it |
|
|
681
|
+
| A step reported `skipped` but nothing can search | The step tested for `manifest.json` instead of asking `ready` |
|
|
682
|
+
| `provider "openai": no api key` | `zen key ls` - the keyring, or a real environment variable |
|
|
683
|
+
| A usage error before any credential is asked for | Deliberate: everything about the invocation is checked first, so a typo is a typo |
|
|
684
|
+
| Nothing matched | Exit 0 with an empty answer. Try fewer words, or `--all` instead of a narrow field |
|
|
685
|
+
| Answers about the wrong version of the API | Nothing watches the document. Re-index after it changes |
|
|
686
|
+
| A search took ten seconds, using no CPU | One embedding round trip, not the index. `list`/`grep` make none |
|
|
687
|
+
| `search <word> <word>` gave ranked nonsense | Bare words after `search` are the QUERY, not a subcommand. You meant `list`/`grep` |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: zen-review
|
|
3
|
-
description: Checking a project before committing - the four mechanical checks `/project-review` makes before any judgement (`zen check`, the §2.5 path sweep, the memory house-rules copy, the spec-sync record), what each one owns, what their exit codes mean, and what deliberately has no check because no tool can make it. Load before running `/project-review`, before saying a project is ready, and whenever a review finding needs verifying rather than restating.
|
|
3
|
+
description: Checking a project before committing - the four mechanical checks `/project-review` makes before any judgement (`zen check`, the §2.5 path sweep, the memory house-rules copy, the spec-sync record), what each one owns, what their exit codes mean, which findings hand off to `zen-instructions`, and what deliberately has no check because no tool can make it. Load before running `/project-review`, before saying a project is ready, and whenever a review finding needs verifying rather than restating.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Reviewing a project
|
|
@@ -92,6 +92,22 @@ cause and the least believable finding.
|
|
|
92
92
|
`agents/memory-policy-instructions.md`, which filename order puts directly after
|
|
93
93
|
the copy - see the `zen-memory` skill.
|
|
94
94
|
|
|
95
|
+
## Findings that are not yours to fix in place
|
|
96
|
+
|
|
97
|
+
`zen check` reports `rules.stale`, `files.uninstructed`, `memory.uninstructed`,
|
|
98
|
+
`fork.uninstructed`, `rules.requires.unknown` or `rules.unreached` against a
|
|
99
|
+
file under `agents/`, and the obvious repair - open it and edit it - is wrong for
|
|
100
|
+
four of those files. `tools-instructions.md`, `files-instructions.md`,
|
|
101
|
+
`memory-instructions.md` and `fork-instructions.md` are `zen`'s copies:
|
|
102
|
+
`zen check --fix` replaces them, so an edit made inside one is gone at the next
|
|
103
|
+
fix, `init` or `open`.
|
|
104
|
+
|
|
105
|
+
**Load `zen-instructions` before acting on any of those findings**, and before
|
|
106
|
+
judging a house rule at all. It owns which documents belong to whom, where a
|
|
107
|
+
project's own rules on the same subject go instead, what `requires:` does, and
|
|
108
|
+
what filename order decides. A review that fixes one of ours in place has
|
|
109
|
+
undone itself by the next `zen open` and left nothing behind to say so.
|
|
110
|
+
|
|
95
111
|
## What has no check, on purpose
|
|
96
112
|
|
|
97
113
|
Not because it would be hard, but because the answer would be wrong:
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: zen-sandbox
|
|
3
|
+
description: How the container sandbox works, when and how to configure it in `agents.yaml` (`sandbox:` and `agents[].sandbox`), the four `sandbox:*` tools, mount layout (/workspace, $HOME, /tmp, /run/zenera/keys), lifecycle and persistence (`persist: true`), credential forwarding by name, resource ceilings (cpus, memory, pids), standard vs strict hardening profiles, path rules, and debugging container issues with `zen sandbox`. Refers to `zen-cli` (`.github/skills/zen-cli/references/sandbox.md`).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# The Container Sandbox
|
|
7
|
+
|
|
8
|
+
A shell, and a boundary around it. The boundary is a Linux container: the
|
|
9
|
+
agent's commands run against an image the project chose, with the session's
|
|
10
|
+
workspace bind-mounted at `/workspace` and nothing else of the host machine in
|
|
11
|
+
reach.
|
|
12
|
+
|
|
13
|
+
The container is the boundary rather than any parsing or filtering of what the
|
|
14
|
+
model wrote. On the host side, every process is spawned with `shell: false` and
|
|
15
|
+
arguments assembled as an array; command text travels exclusively down **stdin**
|
|
16
|
+
into `/bin/sh -s` inside the container. There is no host shell to inject into,
|
|
17
|
+
and no argv length to overflow.
|
|
18
|
+
|
|
19
|
+
The container engine is **Podman** (rootless by default).
|
|
20
|
+
|
|
21
|
+
For the CLI subcommands (`zen sandbox status`, `up`, `pull`, `clean`, `disk`)
|
|
22
|
+
and command-line options, see the [zen-cli skill](../zen-cli/SKILL.md) and its
|
|
23
|
+
dedicated reference document [sandbox.md](../zen-cli/references/sandbox.md).
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 1. The Sandbox Tools (`sandbox:*`)
|
|
28
|
+
|
|
29
|
+
When an agent is granted sandbox tools in `agents.yaml`, it receives up to four
|
|
30
|
+
tools from the `sandbox` group:
|
|
31
|
+
|
|
32
|
+
| Tool | Type | Description |
|
|
33
|
+
| ------------------------ | ---------- | --------------------------------------------------------------------- |
|
|
34
|
+
| `run_command` | foreground | Runs a shell command under `/bin/sh`, waits for exit, captures output |
|
|
35
|
+
| `run_command_background` | background | Starts a detached command (server, watcher) and returns a `job_id` |
|
|
36
|
+
| `read_command_output` | inspection | Reads a window of log lines from a background job and checks status |
|
|
37
|
+
| `stop_command` | control | Signals and terminates a background job process group |
|
|
38
|
+
|
|
39
|
+
### `run_command`
|
|
40
|
+
|
|
41
|
+
- **Parameters:**
|
|
42
|
+
- `command` (string, required): the shell command string, interpreted by `/bin/sh` inside the container.
|
|
43
|
+
- `cwd` (string, optional): working directory relative to `/workspace`. Defaults to `/workspace`.
|
|
44
|
+
- `timeout` (integer, optional): seconds before SIGTERM/SIGKILL. Defaults to 120 s; capped at 3600 s.
|
|
45
|
+
- **Output:** returns `exit_code`, `stdout`, `stderr`, `duration_ms`. Combined output is capped at 64 KiB (`truncated: true` when exceeded).
|
|
46
|
+
|
|
47
|
+
### `run_command_background`
|
|
48
|
+
|
|
49
|
+
- **Parameters:** `command` (string, required), `cwd` (string, optional).
|
|
50
|
+
- **Execution:** written to `/tmp/zenera-jobs/<job_id>.sh` and started in a separate session via `setsid`. Output and exit status stream to `/tmp/zenera-jobs/<job_id>.log` and `<job_id>.exit`.
|
|
51
|
+
- **Returns:** `{ job_id, started: true }`.
|
|
52
|
+
|
|
53
|
+
### `read_command_output`
|
|
54
|
+
|
|
55
|
+
- **Parameters:** `job_id` (string, required), `start_line` (integer, optional, 1-based).
|
|
56
|
+
- **Returns:** `{ job_id, running: boolean, exit_code?: number, output: string, start_line, end_line, lines }`. Displays up to 400 lines per call.
|
|
57
|
+
|
|
58
|
+
### `stop_command`
|
|
59
|
+
|
|
60
|
+
- **Parameters:** `job_id` (string, required).
|
|
61
|
+
- **Behaviour:** sends SIGTERM to the job's entire process group (`-$pid`), waits 1 s grace period, then sends SIGKILL.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 2. Tool Selection in `agents.yaml`
|
|
66
|
+
|
|
67
|
+
Agents select tools through `tools:` using exact names, group wildcards, or subtraction:
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
agents:
|
|
71
|
+
- name: developer
|
|
72
|
+
tools: [files:*, sandbox:*] # all file and shell tools
|
|
73
|
+
|
|
74
|
+
- name: reviewer
|
|
75
|
+
tools: [files:*, sandbox:run_command] # foreground only, no background servers
|
|
76
|
+
|
|
77
|
+
- name: tester
|
|
78
|
+
tools: [sandbox:*, -stop_command] # sandbox tools without stop_command
|
|
79
|
+
|
|
80
|
+
- name: analyst
|
|
81
|
+
tools: [files:*] # no shell tools; container is never started
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
An agent with no `sandbox:*` tools never triggers an image pull, container creation, or VM startup.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 3. Filesystem and Mount Layout
|
|
89
|
+
|
|
90
|
+
Inside the container, paths are isolated to designated mount points:
|
|
91
|
+
|
|
92
|
+
| Path inside container | Host Source | Mode | Lifecycle / Purpose |
|
|
93
|
+
| --------------------- | ------------------------- | ----------- | ------------------------------------------------------ |
|
|
94
|
+
| `/workspace` | Session workspace | read-write* | The work itself. Changes survive session completion. |
|
|
95
|
+
| `/home/agent` | `.data/sandbox/home` | read-write | User home (`$HOME`). Persists across sessions. |
|
|
96
|
+
| `/tmp` | In-memory / host tmpfs | read-write | Ephemeral scratch; holds `/tmp/zenera-jobs`. |
|
|
97
|
+
| `/memory` | Session memory directory | read-only | Generated memory files (when `memory:` is enabled). |
|
|
98
|
+
| `/assets` | Project assets directory | read-only | Reference assets declared in `SPECIFICATION.md`. |
|
|
99
|
+
| `/skills` | Project skills directory | read-only | Custom skill files available to agents. |
|
|
100
|
+
| `/run/zenera/keys` | Keychain / temp directory | read-only | Forwarded file credentials (e.g. Vertex service keys). |
|
|
101
|
+
|
|
102
|
+
_\*Mounted read-only if the session or project was opened in read-only mode._
|
|
103
|
+
|
|
104
|
+
### Path Rules
|
|
105
|
+
|
|
106
|
+
- All relative paths and `cwd` arguments resolve against `/workspace`.
|
|
107
|
+
- Lexical escapes outside `/workspace` (`../../etc`, absolute `/etc`, null bytes `\0`) are **strictly rejected** by the runtime and return an actionable error to the model.
|
|
108
|
+
- Path resolution inside the container never resolves against host symlinks.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 4. Lifecycle, Container Naming, and Persistence
|
|
113
|
+
|
|
114
|
+
### Lazy Initialization
|
|
115
|
+
|
|
116
|
+
Containers are started on-demand. If an agent never invokes a `sandbox:*` tool, no image is pulled and no container runs. Concurrent tool calls in the same turn await a single startup promise without racing.
|
|
117
|
+
|
|
118
|
+
### Deterministic Container Naming
|
|
119
|
+
|
|
120
|
+
Container names follow the pattern:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
zn-<key>-<digest>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
- `<key>` is derived from the session identifier or project key.
|
|
127
|
+
- `<digest>` is the first 10 hex characters of the SHA-256 hash of the configuration:
|
|
128
|
+
`[root, image, hashCpus, hashMemory, network, workdir, user, readOnly, mounts, env, secrets, hardening]`
|
|
129
|
+
|
|
130
|
+
### The Persistence Rule (`persist: true` vs `persist: false`)
|
|
131
|
+
|
|
132
|
+
- **`persist: false` (ephemeral):** when the session finishes, the container is deleted (`podman rm --force --volumes`). Changes made to `/workspace` and `/home/agent` survive on the host; changes made to the root filesystem (such as `apt-get` or root `pip install`) are discarded.
|
|
133
|
+
- **`persist: true` (recommended for development):** the container is stopped (`podman stop --time 2`) rather than removed. System packages and global caches survive between runs.
|
|
134
|
+
- **Caveat:** because the container name depends on its configuration, **any edit to `sandbox:` in `agents.yaml` generates a new container name**, abandoning the persisted container rootfs. When dependencies must be permanent across config changes, bake them into the image using `sandbox.build`.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 5. Configuring `sandbox:` in `agents.yaml`
|
|
139
|
+
|
|
140
|
+
Configured at the top level and optionally overridden per agent under `agents[].sandbox`:
|
|
141
|
+
|
|
142
|
+
```yaml
|
|
143
|
+
sandbox:
|
|
144
|
+
persist: true
|
|
145
|
+
image: docker.io/library/python:3.14-slim-bookworm
|
|
146
|
+
cpus: 4
|
|
147
|
+
memory: 4096
|
|
148
|
+
network: bridge
|
|
149
|
+
timeout: 120
|
|
150
|
+
hardening: standard
|
|
151
|
+
env: [HTTPS_PROXY, NO_PROXY]
|
|
152
|
+
keys: true
|
|
153
|
+
|
|
154
|
+
agents:
|
|
155
|
+
- name: builder
|
|
156
|
+
# Inherits base sandbox configuration
|
|
157
|
+
|
|
158
|
+
- name: isolated-runner
|
|
159
|
+
sandbox:
|
|
160
|
+
hardening: strict
|
|
161
|
+
network: none
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Configuration Fields
|
|
165
|
+
|
|
166
|
+
| Field | Type | Default | Meaning |
|
|
167
|
+
| ----------- | ---------------------- | --------------------------------------------- | ------------------------------------------------------- |
|
|
168
|
+
| `image` | string | `docker.io/library/python:3.14-slim-bookworm` | Base image to run. Mutually exclusive with `build`. |
|
|
169
|
+
| `build` | object | none | `{ dockerfile, context? }`. Builds a local Dockerfile. |
|
|
170
|
+
| `cpus` | number | `4` (`2` in `strict`) | Fractional CPU limit (`--cpus`). `0` for unconstrained. |
|
|
171
|
+
| `memory` | integer (MiB) | `4096` (`2048` in `strict`) | Memory limit (`--memory`). `0` for unconstrained. |
|
|
172
|
+
| `network` | `bridge`/`none`/`host` | `bridge` (`none` in `strict`) | Network connectivity mode. |
|
|
173
|
+
| `workdir` | absolute path | `/workspace` | Working directory and workspace mount destination. |
|
|
174
|
+
| `timeout` | integer (seconds) | `120` | Default per-command execution timeout. |
|
|
175
|
+
| `hardening` | `standard`/`strict` | `standard` | Security posture (see §6). |
|
|
176
|
+
| `user` | string | image user | `uid`, `name`, or `uid:gid` inside the container. |
|
|
177
|
+
| `persist` | boolean | `false` | Keep container filesystem between runs. |
|
|
178
|
+
| `env` | string[] | `[]` | Host environment variables to pass by name. |
|
|
179
|
+
| `keys` | boolean | `true` | Forward model credentials to the container. |
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 6. Hardening Postures: `standard` vs `strict`
|
|
184
|
+
|
|
185
|
+
Every container runs with baseline safety controls:
|
|
186
|
+
|
|
187
|
+
- `--security-opt no-new-privileges` prevents setuid escalation.
|
|
188
|
+
- `--init` provides a dedicated init process (catatonit) to reap zombie background processes.
|
|
189
|
+
- `--pids-limit 1024` halts fork bombs.
|
|
190
|
+
- `--restart no` prevents runaway resurrection.
|
|
191
|
+
- Never `--privileged`.
|
|
192
|
+
- Podman's default seccomp filter is always enabled.
|
|
193
|
+
|
|
194
|
+
### Comparison
|
|
195
|
+
|
|
196
|
+
| Security Dimension | `standard` (default) | `strict` |
|
|
197
|
+
| -------------------- | ------------------------------------------- | ------------------------------------------------- |
|
|
198
|
+
| **Intended Use** | General development, testing, builds | Executing unreviewed or untrusted code |
|
|
199
|
+
| **Kernel Caps** | Default set kept (allows `apt`, `pip`) | `--cap-drop ALL` (all capabilities dropped) |
|
|
200
|
+
| **Root Filesystem** | Writable | `--read-only` (immutable rootfs) |
|
|
201
|
+
| **Temporary Space** | Standard `/tmp` | `--tmpfs /tmp:rw,noexec,nosuid,nodev,size=64m` |
|
|
202
|
+
| **User Namespace** | Rootless default (uid 0 inside = host user) | `--userns keep-id` (host uid inside, owns mounts) |
|
|
203
|
+
| **Default Network** | `bridge` | `none` |
|
|
204
|
+
| **Default Ceilings** | `cpus: 4`, `memory: 4096` | `cpus: 2`, `memory: 2048` |
|
|
205
|
+
| **Package Installs** | Allowed (`apt-get`, `pip`, `npm`) | Refused (must pre-bake into Dockerfile) |
|
|
206
|
+
| **Workspace** | `/workspace` is read-write | `/workspace` is read-write |
|
|
207
|
+
|
|
208
|
+
### Key Strict Mode Nuances
|
|
209
|
+
|
|
210
|
+
1. **Pre-baked dependencies:** because `/` is read-only and caps are dropped, dynamic package installations fail. Dependencies must be defined in `sandbox/Dockerfile` using `sandbox.build`.
|
|
211
|
+
2. **`keep-id` user mapping:** under rootless Podman, `--userns keep-id` maps your host UID to the container UID. Files written to `/workspace` remain owned by you on the host, and the container user cannot access root-only files inside the image. If an explicit `user:` is specified in config, `keep-id` is omitted.
|
|
212
|
+
3. **`noexec` on `/tmp`:** prevents running downloaded binaries directly from scratch space. Interpreted scripts (`/bin/sh /tmp/script.sh` or `python /tmp/test.py`) remain functional because the interpreter executable lives in the read-only root.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## 7. Credentials and Secret Forwarding
|
|
217
|
+
|
|
218
|
+
1. **Forwarded by Name, Never by Value:**
|
|
219
|
+
Host environment variables and credentials are passed using Podman's `--env NAME` syntax (without an `=value`). Podman pulls values from its own environment at execution time. Secrets never appear on command-line arguments, in process lists (`ps`), or in `podman inspect`.
|
|
220
|
+
2. **Env Name Blacklist:**
|
|
221
|
+
The `env:` array in `agents.yaml` strictly refuses variables named `KEY`, `TOKEN`, `SECRET`, `PASSWORD`, `PASSWD`, or `CREDENTIAL` to prevent accidental credential leakage into uninspected scripts.
|
|
222
|
+
3. **File-Shaped Credentials:**
|
|
223
|
+
Key files (such as Google Vertex service account keys) are mounted read-only to `/run/zenera/keys/` and their environment variables (e.g. `GOOGLE_APPLICATION_CREDENTIALS`) are automatically rewritten to point to the in-container path.
|
|
224
|
+
4. **Disabling Forwarding:**
|
|
225
|
+
Set `keys: false` in `agents.yaml` or launch runs with `zen run --no-keys` to prevent any model API keys or service credentials from reaching the container.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 8. Multi-Agent Sandboxes (`SandboxPool`)
|
|
230
|
+
|
|
231
|
+
- Two agents with identical sandbox configurations share the **same container**.
|
|
232
|
+
- Two agents with different images or configurations receive separate containers.
|
|
233
|
+
- The `sandbox:*` tool definitions remain uniform across the session; when an agent calls a tool, the pool routes the call to that specific agent's container at invocation time.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## 9. Diagnostics and Troubleshooting (`zen sandbox`)
|
|
238
|
+
|
|
239
|
+
Use the `zen sandbox` commands via terminal to inspect and manage container state:
|
|
240
|
+
|
|
241
|
+
```sh
|
|
242
|
+
zen sandbox status # Check podman version, machine status, and active containers
|
|
243
|
+
zen sandbox up # Start VM (macOS) and pull or build the required image
|
|
244
|
+
zen sandbox pull # Re-pull base image or force build of sandbox/Dockerfile
|
|
245
|
+
zen sandbox clean # Force remove all zn-* containers created by this project
|
|
246
|
+
zen sandbox disk # Inspect disk space consumed by images, containers, and volumes
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Exit Codes and Common Issues
|
|
250
|
+
|
|
251
|
+
- **Exit Code 5:** Container engine failed or image build failed.
|
|
252
|
+
- On macOS: ensure Podman machine is running (`zen sandbox up` or `podman machine start`).
|
|
253
|
+
- On Linux: verify rootless subuid/subgid mapping is configured (`/etc/subuid`).
|
|
254
|
+
- **Permission Denied in `/workspace`:**
|
|
255
|
+
- In `strict` mode without `user:`, rootless Podman uses `keep-id` to preserve host UID. If using custom `user:`, verify host directory permissions allow that UID.
|
|
256
|
+
- **Disk Space Pressure:**
|
|
257
|
+
- Run `zen sandbox clean` to prune stopped session containers.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: zen-spec-sync
|
|
3
|
-
description: Running `/spec-sync-project` as an incremental pass - the committed `.spec-sync` baseline of what the last pass applied, how to scope a pass from the specification and feedback diffs and from what moved in the project itself, the
|
|
3
|
+
description: Running `/spec-sync-project` as an incremental pass - the committed `.spec-sync` baseline of what the last pass applied, how to scope a pass from the specification and feedback diffs and from what moved in the project itself, the five checks a narrow pass may never skip, which skills a pass must load before editing (`zen-instructions` for any house rule it touches), the iteration history format, and why the baseline is written last. Load before every `/spec-sync-project`, including the first one on a project that has no baseline yet.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Syncing a project with its specification, one change at a time
|
|
@@ -103,7 +103,7 @@ editing anything, and report it in the chat as the pass's scope.
|
|
|
103
103
|
covers it, and you say which line, or it does not, and it is an **extra**
|
|
104
104
|
reported under `➕`.
|
|
105
105
|
|
|
106
|
-
## The
|
|
106
|
+
## The five checks a narrow pass may never skip
|
|
107
107
|
|
|
108
108
|
However small the diff:
|
|
109
109
|
|
|
@@ -111,7 +111,12 @@ However small the diff:
|
|
|
111
111
|
last.
|
|
112
112
|
2. **`scripts/_setup.sh` has run to completion in this session, and re-runs
|
|
113
113
|
clean.** A one-line specification change can still break a setup step, and a
|
|
114
|
-
pass that did not run it does not know.
|
|
114
|
+
pass that did not run it does not know. Then `scripts/_setup.sh --check` must
|
|
115
|
+
report every step `skipped`: it makes each step's own readiness test without
|
|
116
|
+
doing the work, which is what tells a real `skipped` from a step that only
|
|
117
|
+
looked done. A step that tests for a committed `manifest.json` skips on a
|
|
118
|
+
fresh clone whose git-ignored `lance/` vectors are missing, and the project
|
|
119
|
+
cannot search.
|
|
115
120
|
3. **§9 of the copilot instructions, worked as a checklist**, group by group.
|
|
116
121
|
4. **The capability obligations in §0.1.** For every capability the project has
|
|
117
122
|
turned on, load that capability's skill and re-check what it requires - in
|
|
@@ -119,6 +124,14 @@ However small the diff:
|
|
|
119
124
|
`.github/skills/zen-memory/references/memory-instructions.md`. `zen init` and
|
|
120
125
|
`zen open` rewrite the reference, so this drifts without anybody editing the
|
|
121
126
|
project.
|
|
127
|
+
5. **`zen-instructions` is loaded before any house rule is touched.** A pass
|
|
128
|
+
whose scope reaches `agents/*instructions.md` - and input 4 puts it there
|
|
129
|
+
whenever one of them moved - has to know that `tools-instructions.md`,
|
|
130
|
+
`files-instructions.md`, `memory-instructions.md` and `fork-instructions.md`
|
|
131
|
+
are `zen`'s and are replaced by `zen check --fix`. A pass that writes a
|
|
132
|
+
requirement into one of them has implemented nothing: the next `open` reverts
|
|
133
|
+
it, the history entry claims it, and the baseline records it as applied.
|
|
134
|
+
Specification-driven rules go in a topic file of the project's own beside it.
|
|
122
135
|
|
|
123
136
|
A narrow diff narrows reading and editing. It does not narrow verification.
|
|
124
137
|
|
|
@@ -527,6 +527,11 @@
|
|
|
527
527
|
"type": "integer",
|
|
528
528
|
"exclusiveMinimum": 0
|
|
529
529
|
},
|
|
530
|
+
"hardening": {
|
|
531
|
+
"description": "How much of the host the container may be. `standard` keeps the root filesystem writable and capabilities intact so package managers work. `strict` drops every capability, mounts the root filesystem read-only, restricts /tmp to a 64 MiB noexec tmpfs, and defaults network to none.",
|
|
532
|
+
"type": "string",
|
|
533
|
+
"enum": ["standard", "strict"]
|
|
534
|
+
},
|
|
530
535
|
"user": {
|
|
531
536
|
"description": "uid, name or `uid:gid`. Unset means the image's own user.",
|
|
532
537
|
"type": "string",
|
|
@@ -572,12 +577,12 @@
|
|
|
572
577
|
"$ref": "#/$defs/modelRef"
|
|
573
578
|
},
|
|
574
579
|
"tools": {
|
|
575
|
-
"description": "Tool names and selectors: `workspace:*` is every file tool, `sandbox:*` the shell, `exa:*` web search and page reading. Subtract from a group with a leading `-`.",
|
|
580
|
+
"description": "Tool names and selectors: `files:*` (alias `workspace:*`) is every file tool, `sandbox:*` the shell, `exa:*` web search and page reading. Subtract from a group with a leading `-`.",
|
|
576
581
|
"type": "array",
|
|
577
582
|
"items": { "type": "string", "minLength": 1 },
|
|
578
583
|
"examples": [
|
|
579
|
-
["
|
|
580
|
-
["
|
|
584
|
+
["files:*", "sandbox:*"],
|
|
585
|
+
["files:*", "-delete_file"]
|
|
581
586
|
]
|
|
582
587
|
},
|
|
583
588
|
"handoffs": {
|
|
@@ -26,7 +26,10 @@ something in it — and report what was done in enough detail to be checked.
|
|
|
26
26
|
an index built, a document fetched, a file generated.
|
|
27
27
|
- `scripts/_setup.sh` is the one command that does it, and `scripts/<name>.sh`
|
|
28
28
|
are the steps it runs in order. A step is safe to run twice and reports
|
|
29
|
-
`skipped` the second time
|
|
29
|
+
`skipped` the second time — testing for something that survives a clone, not
|
|
30
|
+
for a file `.gitignore` excludes.
|
|
31
|
+
- `scripts/_setup.sh --check` asks the same questions and does none of the work,
|
|
32
|
+
so what is missing can be read without paying to find out.
|
|
30
33
|
- Currently: no steps. Nothing has to be prepared before a run.
|
|
31
34
|
|
|
32
35
|
## Agents
|