@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.
- package/README.md +3 -3
- package/dist/banner.d.ts +41 -20
- package/dist/banner.js +32 -5
- package/dist/commands/check.js +1 -0
- 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 +1 -0
- package/dist/commands/models.js +1 -0
- 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 +3 -2
- package/dist/lib.js +3 -2
- 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.js +3 -1
- 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 +82 -3
- package/package.json +2 -2
- package/templates/editor/.github/copilot-instructions.md +62 -33
- package/templates/editor/.github/prompts/spec-sync-project.prompt.md +31 -1
- package/templates/editor/.github/skills/zen-cli/SKILL.md +15 -15
- package/templates/editor/.github/skills/zen-cli/references/projects.md +1 -0
- package/templates/editor/.github/skills/zen-cli/references/rag.md +37 -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-warmup/SKILL.md +554 -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 +15 -3
- package/templates/editor/.vscode/agents.schema.json +8 -3
- package/templates/project/SPECIFICATION.md.tmpl +2 -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 +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
|
|
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,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
|
-
["
|
|
580
|
-
["
|
|
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
|
|