@zenera/cli 1.1.20 → 1.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +3 -3
  2. package/dist/banner.d.ts +41 -20
  3. package/dist/banner.js +32 -5
  4. package/dist/commands/check.js +1 -0
  5. package/dist/commands/init.js +1 -0
  6. package/dist/commands/inspect.js +1 -0
  7. package/dist/commands/key.js +1 -0
  8. package/dist/commands/list.js +1 -0
  9. package/dist/commands/memory.js +1 -0
  10. package/dist/commands/models.js +1 -0
  11. package/dist/commands/open.js +1 -0
  12. package/dist/commands/run.js +3 -0
  13. package/dist/commands/sandbox.js +1 -0
  14. package/dist/commands/version.js +1 -0
  15. package/dist/lib.d.ts +3 -2
  16. package/dist/lib.js +3 -2
  17. package/dist/meta.d.ts +1 -1
  18. package/dist/meta.js +5 -28
  19. package/dist/resolve.js +19 -12
  20. package/dist/sandbox.js +1 -0
  21. package/dist/scaffold.js +3 -1
  22. package/dist/term.d.ts +2 -0
  23. package/dist/term.js +2 -0
  24. package/dist/tui/markdown.d.ts +16 -0
  25. package/dist/tui/markdown.js +259 -0
  26. package/dist/tui/theme.js +1 -1
  27. package/dist/tui/wrap.d.ts +1 -1
  28. package/dist/validate.js +82 -3
  29. package/package.json +2 -2
  30. package/templates/editor/.github/copilot-instructions.md +62 -33
  31. package/templates/editor/.github/prompts/spec-sync-project.prompt.md +31 -1
  32. package/templates/editor/.github/skills/zen-cli/SKILL.md +15 -15
  33. package/templates/editor/.github/skills/zen-cli/references/projects.md +1 -0
  34. package/templates/editor/.github/skills/zen-cli/references/rag.md +37 -3
  35. package/templates/editor/.github/skills/zen-cli/references/sandbox.md +30 -2
  36. package/templates/editor/.github/skills/zen-instructions/SKILL.md +232 -0
  37. package/templates/editor/.github/skills/zen-memory-warmup/SKILL.md +554 -0
  38. package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +56 -23
  39. package/templates/editor/.github/skills/zen-rag-schema/SKILL.md +55 -28
  40. package/templates/editor/.github/skills/zen-review/SKILL.md +17 -1
  41. package/templates/editor/.github/skills/zen-sandbox/SKILL.md +257 -0
  42. package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +15 -3
  43. package/templates/editor/.vscode/agents.schema.json +8 -3
  44. package/templates/project/SPECIFICATION.md.tmpl +2 -1
  45. package/templates/project/agents/files-instructions.md +86 -0
  46. package/templates/project/agents/fork-instructions.md +17 -0
  47. package/templates/project/agents/memory-instructions.md +37 -0
  48. package/templates/project/agents.yaml.tmpl +3 -3
  49. package/templates/project/gitignore +12 -1
  50. package/templates/project/scripts/_setup.sh +19 -4
@@ -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 | Answers | Embedder? | Typical |
25
- | -------- | -------------------------------------- | --------- | ------- |
26
- | `index` | builds the thing | yes | minutes |
27
- | `search` | _what is this API's way to do X?_ | **yes** | seconds |
28
- | `list` | _what methods/types/fields are there?_ | no | instant |
29
- | `grep` | _does the string X appear anywhere?_ | no | instant |
30
- | `trace` | _which call can reach this field?_ | no | instant |
31
- | `show` | _print exactly these things_ | no | instant |
32
- | `stats` | _what is in this index?_ | no | instant |
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; re-index or pass the right `--embedding` |
654
- | No manifest / not an index | A build that did not finish. `manifest.json` is written last on purpose |
655
- | `provider "openai": no api key` | `zen key ls` - the keyring, or a real environment variable |
656
- | A usage error before any credential is asked for | Deliberate: everything about the invocation is checked first, so a typo is a typo |
657
- | Nothing matched | Exit 0 with an empty answer. Try fewer words, or `--all` instead of a narrow field |
658
- | Answers about the wrong version of the API | Nothing watches the document. Re-index after it changes |
659
- | A search took ten seconds, using no CPU | One embedding round trip, not the index. `list`/`grep` make none |
660
- | `search <word> <word>` gave ranked nonsense | Bare words after `search` are the QUERY, not a subcommand. You meant `list`/`grep` |
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 four checks a narrow pass may never skip, 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.
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 four checks a narrow pass may never skip
106
+ ## The five checks a narrow pass may never skip
107
107
 
108
108
  However small the diff:
109
109
 
@@ -111,7 +111,11 @@ 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. `skipped` is only a pass if the
115
+ artefact really is usable: for every index the project builds, check
116
+ `zen rag <subject> ready --dir <dir>` agrees. A step that tests for a
117
+ committed `manifest.json` skips on a fresh clone whose git-ignored `lance/`
118
+ vectors are missing, and the project cannot search.
115
119
  3. **§9 of the copilot instructions, worked as a checklist**, group by group.
116
120
  4. **The capability obligations in §0.1.** For every capability the project has
117
121
  turned on, load that capability's skill and re-check what it requires - in
@@ -119,6 +123,14 @@ However small the diff:
119
123
  `.github/skills/zen-memory/references/memory-instructions.md`. `zen init` and
120
124
  `zen open` rewrite the reference, so this drifts without anybody editing the
121
125
  project.
126
+ 5. **`zen-instructions` is loaded before any house rule is touched.** A pass
127
+ whose scope reaches `agents/*instructions.md` - and input 4 puts it there
128
+ whenever one of them moved - has to know that `tools-instructions.md`,
129
+ `files-instructions.md`, `memory-instructions.md` and `fork-instructions.md`
130
+ are `zen`'s and are replaced by `zen check --fix`. A pass that writes a
131
+ requirement into one of them has implemented nothing: the next `open` reverts
132
+ it, the history entry claims it, and the baseline records it as applied.
133
+ Specification-driven rules go in a topic file of the project's own beside it.
122
134
 
123
135
  A narrow diff narrows reading and editing. It does not narrow verification.
124
136
 
@@ -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
- ["workspace:*", "sandbox:*"],
580
- ["workspace:*", "-delete_file"]
584
+ ["files:*", "sandbox:*"],
585
+ ["files:*", "-delete_file"]
581
586
  ]
582
587
  },
583
588
  "handoffs": {
@@ -26,7 +26,8 @@ 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.
30
31
  - Currently: no steps. Nothing has to be prepared before a run.
31
32
 
32
33
  ## Agents
@@ -0,0 +1,86 @@
1
+ ---
2
+ requires: [files]
3
+ ---
4
+
5
+ # Working with files
6
+
7
+ These rules govern how files are discovered, inspected, and modified. If file tools
8
+ are not among your tools, you cannot read or change files directly.
9
+
10
+ ## Access modes
11
+
12
+ Check which tools you actually have:
13
+
14
+ - If `write_file` or `apply_patch` is not among your tools, your access is **read-only**.
15
+ Inspect files, search directories, and report conclusions, but attempt no edits.
16
+ - If `write_file` and `apply_patch` are available, you may create and edit files
17
+ under `/workspace`.
18
+
19
+ ## Discovery before assumption
20
+
21
+ Never guess a file's name, location, or contents.
22
+
23
+ - Use `list_dir` to see directory contents and formats before opening files.
24
+ - Use `find_files` to locate files by substring when the layout is unfamiliar.
25
+ - Check whether a target file already exists before writing to it.
26
+
27
+ ## Reading files
28
+
29
+ - Use `read_file` to read text files.
30
+ - For files longer than a few dozen lines, pass `start_line` and `end_line` (1-based, inclusive)
31
+ to read only the relevant section.
32
+ - When `read_file` returns `truncated: true`, the read hit the size limit or range boundary.
33
+ Resume reading from `end_line + 1` if you need the remainder.
34
+ - Non-text files (images, binaries, archives) cannot be read as text; `list_dir` reports
35
+ their format.
36
+
37
+ ## Modifying files
38
+
39
+ ### Modifying existing files: `apply_patch`
40
+
41
+ Always use `apply_patch` to edit existing files. It makes surgical, verifiable changes
42
+ and validates the entire patch before writing any file to disk.
43
+
44
+ 1. **Read before patching**: Read the target file first to obtain exact context lines.
45
+ An outdated or imagined context line fails the patch.
46
+ 2. **Provide context**: Include at least 3 unchanged context lines (prefixed with a space)
47
+ before and after each change.
48
+ 3. **No line numbers**: Patches match on context text, never on line numbers.
49
+ 4. **Always close the patch**: Every patch must begin with `*** Begin Patch` and end
50
+ with `*** End Patch`. Omitting `*** End Patch` is an error.
51
+
52
+ Example patch:
53
+
54
+ ```
55
+ *** Begin Patch
56
+ *** Update File: /workspace/src/config.ts
57
+ @@ port configuration
58
+ export interface Config {
59
+ host: string;
60
+ - port: 80;
61
+ + port: 8080;
62
+ timeoutMs: number;
63
+ }
64
+ *** End Patch
65
+ ```
66
+
67
+ ### Creating new files: `write_file`
68
+
69
+ Use `write_file` only to create a **new** file or to overwrite a small file completely.
70
+ Never use `write_file` to update an existing file when only a few lines need to change.
71
+
72
+ ### Moving and deleting
73
+
74
+ - Use `move_file` to rename or relocate a file or directory. It creates parent directories
75
+ as needed. Set `overwrite: true` only when intentionally replacing an existing destination.
76
+ - Use `delete_file` to remove a file. Removing a directory requires `recursive: true`.
77
+
78
+ ## Mounts and containment
79
+
80
+ All file operations are confined to mounted trees:
81
+
82
+ - `/workspace`: The primary writable directory for the project. Paths inside the workspace
83
+ (such as `/workspace/src/main.ts`) are where project files reside.
84
+ - `/assets`, `/skills`, `/memory`: Read-only mounts. You may read, list, and search them,
85
+ but you cannot write, move, or delete files inside them.
86
+ - Any attempt to reach outside these mounts fails with an containment error.
@@ -28,6 +28,23 @@ exploration down a path that may go nowhere. **Several branches are a fan-out**:
28
28
  independent parts of one task, worked at the same time, merged by you. It is the
29
29
  same call either way; the number of branches is the only difference.
30
30
 
31
+ Every `fork` call takes an array under `branches`, even for a single branch:
32
+
33
+ ```json
34
+ {
35
+ "branches": [
36
+ {
37
+ "name": "audit-auth",
38
+ "agent": "security",
39
+ "instructions": "Audit /workspace/src/auth/** for bypasses. Return the count and worst finding."
40
+ }
41
+ ]
42
+ }
43
+ ```
44
+
45
+ Never pass branch fields (`name`, `instructions`, `agent`) at the top level of
46
+ the tool call.
47
+
31
48
  Do not fork a sequence. Branches run at once and can exchange nothing, so a step
32
49
  that needs the step before it has to stay in this conversation.
33
50