@zenera/cli 1.1.15 → 1.1.17

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 (57) hide show
  1. package/README.md +201 -129
  2. package/dist/banner.d.ts +5 -1
  3. package/dist/banner.js +2 -2
  4. package/dist/catalog.js +19 -20
  5. package/dist/command.d.ts +5 -0
  6. package/dist/commands/check.js +5 -1
  7. package/dist/commands/init.js +41 -6
  8. package/dist/commands/run.js +4 -0
  9. package/dist/engine.js +6 -0
  10. package/dist/env.d.ts +20 -0
  11. package/dist/env.js +65 -0
  12. package/dist/keys.d.ts +2 -0
  13. package/dist/keys.js +2 -0
  14. package/dist/main.js +10 -6
  15. package/dist/sandbox.d.ts +9 -0
  16. package/dist/sandbox.js +23 -3
  17. package/dist/scaffold.d.ts +6 -0
  18. package/dist/scaffold.js +56 -15
  19. package/dist/validate.js +57 -15
  20. package/package.json +2 -2
  21. package/templates/editor/.github/copilot-instructions.md +501 -301
  22. package/templates/editor/.github/prompts/new-agent.prompt.md +6 -6
  23. package/templates/editor/.github/prompts/new-skill.prompt.md +7 -7
  24. package/templates/editor/.github/prompts/review-project.prompt.md +54 -15
  25. package/templates/editor/.github/prompts/sync-with-spec.prompt.md +229 -42
  26. package/templates/editor/.github/prompts/update-spec-from-feedback.prompt.md +142 -0
  27. package/templates/editor/.github/skills/zen-cli/SKILL.md +62 -6
  28. package/templates/editor/.github/skills/zen-cli/references/check.md +9 -6
  29. package/templates/editor/.github/skills/zen-cli/references/faker.md +14 -14
  30. package/templates/editor/.github/skills/zen-cli/references/frame.md +11 -9
  31. package/templates/editor/.github/skills/zen-cli/references/inspect.md +3 -3
  32. package/templates/editor/.github/skills/zen-cli/references/keys.md +9 -9
  33. package/templates/editor/.github/skills/zen-cli/references/memory.md +11 -11
  34. package/templates/editor/.github/skills/zen-cli/references/models.md +8 -8
  35. package/templates/editor/.github/skills/zen-cli/references/projects.md +23 -6
  36. package/templates/editor/.github/skills/zen-cli/references/rag.md +191 -56
  37. package/templates/editor/.github/skills/zen-cli/references/run.md +3 -3
  38. package/templates/editor/.github/skills/zen-cli/references/sandbox.md +14 -8
  39. package/templates/editor/.github/skills/zen-memory/SKILL.md +144 -72
  40. package/templates/editor/.github/skills/zen-memory/scripts/check-instructions.sh +115 -0
  41. package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +71 -48
  42. package/templates/editor/.github/skills/zen-rag-schema/SKILL.md +67 -67
  43. package/templates/editor/.github/skills/zen-review/SKILL.md +123 -0
  44. package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +119 -0
  45. package/templates/editor/.github/skills/zen-review/scripts/review.sh +104 -0
  46. package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +197 -0
  47. package/templates/editor/.github/skills/zen-spec-sync/scripts/snapshot.sh +200 -0
  48. package/templates/editor/.vscode/settings.json +46 -3
  49. package/templates/parts/memory-terms.yaml.tmpl +16 -0
  50. package/templates/parts/memory.yaml.tmpl +12 -0
  51. package/templates/project/SPECIFICATION.md.tmpl +7 -0
  52. package/templates/project/{INSTRUCTIONS.md → agents/instructions.md} +1 -1
  53. package/templates/project/agents/memory-instructions.md +278 -0
  54. package/templates/project/agents.yaml.tmpl +9 -0
  55. package/templates/project/assets/README.md +1 -1
  56. package/templates/project/env +22 -0
  57. package/templates/project/sandbox/Dockerfile.tmpl +3 -0
@@ -1,22 +1,23 @@
1
- # Copilot instructions — agent projects
1
+ # Copilot instructions - agent projects
2
2
 
3
3
  > Written by `zen init` into `.github/copilot-instructions.md`, alongside the
4
4
  > prompt files and skills in the same tree (§2.4). VS Code loads it
5
5
  > automatically for every request in this folder, so it is the standing brief
6
- > for anyone — human or agent — editing this project.
6
+ > for anyone - human or agent - editing this project.
7
7
  >
8
8
  > It describes the Zenera Neo runtime, not this particular project, and `zen`
9
- > rewrites the whole tree on `init` and `open` — so put this project's own
10
- > conventions in `INSTRUCTIONS.md`, where they will survive. The runtime's own
11
- > reference is `docs/agents-yaml.md`, `docs/projects.md` and `DESIGN.md` in the
12
- > @zenera/neo repository — where they disagree with this file, they win.
9
+ > rewrites the whole tree on `init` and `open` - so put this project's own
10
+ > conventions in `agents/instructions.md`, where they will survive. The
11
+ > runtime's own reference is `docs/agents-yaml.md`, `docs/projects.md` and
12
+ > `DESIGN.md` in the @zenera/neo repository - where they disagree with this
13
+ > file, they win.
13
14
 
14
15
  ---
15
16
 
16
17
  ## 0. What this repository is
17
18
 
18
19
  This is **not a normal application repository**. There is no application code
19
- here. It is a folder of declarative artefacts — YAML and Markdown — that
20
+ here. It is a folder of declarative artefacts - YAML and Markdown - that
20
21
  assemble into a running multi-agent system, driven by the `zen` CLI.
21
22
 
22
23
  The centre of gravity is **prose**. Every valuable change here is an edit to a
@@ -34,6 +35,86 @@ programmed: what an agent knows, which model answers, and which of the tools
34
35
  6. Every change must still load: `agents.yaml` is validated strictly at load,
35
36
  and `zen check` says so before a model is ever called.
36
37
 
38
+ ### 0.1 Copilot is the meta-agent: load the skill for every capability in use
39
+
40
+ Copilot is always the meta-agent that creates and changes target projects. Load
41
+ the editor's `zen-cli` skill before deciding how a target project should run,
42
+ be validated, or use command-line capabilities. It is the command contract; do
43
+ not reconstruct `zen` arguments from memory or call `--help` to discover them.
44
+
45
+ **Every runtime capability a project turns on carries obligations that are not
46
+ visible in `agents.yaml` and that `zen check` does not report.** Before
47
+ reviewing or changing a project, list the capabilities it has turned on and load
48
+ the editor skill for each one - `zen-memory` for a `memory:` block, `zen-rag-
49
+ schema` or `zen-rag-docs` for an index, `zen-cli` always. A capability that is
50
+ already configured and already passing `zen check` is exactly the case that
51
+ looks finished and is not: the obligation lives in the skill, so a review that
52
+ never opens the skill cannot find what is missing. This applies whatever
53
+ prompted the change - a task scoped to `SPECIFICATION.md` does not narrow it.
54
+
55
+ First determine whether the target project uses RAG: a `zen rag` command in an
56
+ agent prompt or skill, RAG tools supplied by its host, or a documentation or
57
+ schema index it is expected to consult all count. When it does, also load the
58
+ `zen-cli` skill's `references/rag.md` before writing the target's instructions.
59
+
60
+ Then create or maintain `agents/skills/rag_search/SKILL.md` in the target
61
+ project. Its description must say when an agent should load it. Its body must
62
+ name the target's actual RAG surface and index location, state which agents may
63
+ use it, and give an executable retrieval procedure:
64
+
65
+ 1. Discover document and section names with the exact listing command or tool.
66
+ 2. Start with hybrid search for a complete natural-language question.
67
+ 3. Narrow by document, section, and content kind before repeatedly rewording a
68
+ query; verify facts with exact lookup or a verbatim read.
69
+ 4. Use full-text search for a known literal term, vector search for unknown
70
+ wording, and split text/vector queries only when the exact term is
71
+ distinctive enough to improve the semantic question.
72
+ 5. Retrieve a complete section verbatim, or list a table and read its inclusive
73
+ line range when every row is needed.
74
+
75
+ Keep this as runtime guidance, not a pointer to this editor reference: a target
76
+ agent cannot read `.github/` or this project layout while it runs. Include only
77
+ the commands, tools, paths, and corpus-specific constraints that exist in the
78
+ target project. Grant the agents that need CLI RAG the necessary `sandbox:*`
79
+ tool access; give tool-based RAG users the configured RAG tools instead. Run
80
+ `zen check` after adding or changing the skill.
81
+
82
+ Memory has one such obligation and it is absolute: **a project with a `memory:`
83
+ block carries the `zen-memory` skill's `references/memory-instructions.md` in its
84
+ own `agents/memory-instructions.md`.** `zen init` writes both, since it scaffolds
85
+ memory on; what needs watching is a project that deleted the file, or one whose
86
+ copy has fallen behind the reference. Do not read the two files against each
87
+ other - they drift by trailing whitespace inside tables, which is invisible.
88
+ The skill ships the check:
89
+
90
+ ```sh
91
+ .github/skills/zen-memory/scripts/check-instructions.sh # the verdict
92
+ .github/skills/zen-memory/scripts/check-instructions.sh fix # copy the reference over
93
+ ```
94
+
95
+ It is a copy, never a transcription - the reference ships inside the project,
96
+ because `zen init` and `zen open` write this whole `.github/` tree, so `fix` is
97
+ just `cp` from one to the other.
98
+
99
+ Nothing else tells a running agent how the store works, so without it nothing
100
+ forbids an agent from pointing `grep` or `cat` at `/memory` - a read that
101
+ bypasses the audience mask and serves superseded nodes as current. Keep the copy
102
+ byte-identical and put this project's own memory policy in a second topic file,
103
+ `agents/memory-policy-instructions.md`, which is read straight after it: then the
104
+ copy can be re-run after any `zen init` or `zen open` refreshes the reference,
105
+ without losing anything. Never paraphrase the reference into prose of your own.
106
+ See §5.3.
107
+
108
+ A project with a `SPECIFICATION.md` carries one more: **`.spec-sync/` records
109
+ what the last `/sync-with-spec` pass applied**, and the `zen-spec-sync` skill is
110
+ loaded before running that prompt, whether or not a baseline exists yet. The
111
+ state is committed, because a record only the last machine has is not a record.
112
+ Two things to watch for, neither of which `zen check` reports: a baseline that
113
+ moved without a history entry to match it, which is a pass claiming work it did
114
+ not do, and a `.spec-sync/baseline/` whose `manifest.txt` no longer matches its
115
+ files. Both are reported, not quietly repaired - rewriting the manifest asserts
116
+ a pass that never happened.
117
+
37
118
  ---
38
119
 
39
120
  ## 1. Mental model
@@ -42,15 +123,16 @@ programmed: what an agent knows, which model answers, and which of the tools
42
123
 
43
124
  An agent is four things and nothing more:
44
125
 
45
- | Part | Where it lives | What it decides |
46
- | --------------- | ---------------------------------------------- | ----------------------------- |
47
- | **Instruction** | `INSTRUCTIONS.md` + `agents/prompts/<name>.md` | How it behaves |
48
- | **Model** | `agents.yaml` → `model:` | How well and how expensively |
49
- | **Tools** | `agents.yaml` → `tools:` | What it can _do_ |
50
- | **Knowledge** | `agents/skills/*` + memory | What it can _know_, on demand |
126
+ | Part | Where it lives | What it decides |
127
+ | --------------- | ------------------------------------------------------ | ----------------------------- |
128
+ | **Instruction** | `agents/*instructions.md` + `agents/prompts/<name>.md` | How it behaves |
129
+ | **Model** | `agents.yaml` → `model:` | How well and how expensively |
130
+ | **Tools** | `agents.yaml` → `tools:` | What it can _do_ |
131
+ | **Knowledge** | `agents/skills/*` + memory | What it can _know_, on demand |
132
+
133
+ Plus two relations: **handoffs** - which other agents it may transfer control to
51
134
 
52
- Plus two relations: **handoffs** — which other agents it may transfer control to
53
- — and **fork** — whether it may split into parallel branches at all (§6.4).
135
+ - and **fork** - whether it may split into parallel branches at all (§6.4).
54
136
 
55
137
  There is no hidden orchestration layer. If behaviour is wrong, one of those six
56
138
  things is wrong.
@@ -81,7 +163,7 @@ is deciding **what is in that sequence and in what order**.
81
163
  ```
82
164
  ┌─────────────────────────────────────────────┐
83
165
  │ tool schemas fixed at load │ ← stable, cacheable
84
- │ INSTRUCTIONS.md shared by all agents │ ← stable, cacheable
166
+ │ agents/*instructions shared by all agents │ ← stable, cacheable
85
167
  │ agent prompt this agent's brief │ ← stable per agent
86
168
  │ skill index names + descriptions │ ← stable per agent
87
169
  │ preloaded skills activated turn 0 │ ← stable, in the cached prefix
@@ -93,8 +175,8 @@ is deciding **what is in that sequence and in what order**.
93
175
  Two consequences that drive nearly every design rule in this document:
94
176
 
95
177
  - **Stable prefix = cache hit.** Providers cache by prefix. Anything appended
96
- mid-run _after_ the first reply — a tool schema, an instruction, a late skill
97
- activation — invalidates the cache from that point on. This is why tool schemas
178
+ mid-run _after_ the first reply - a tool schema, an instruction, a late skill
179
+ activation - invalidates the cache from that point on. This is why tool schemas
98
180
  are fixed at load and skill-owned tools are declared from turn 0 and merely
99
181
  _gated_ at call time.
100
182
  - **Everything in the prefix is paid for on every call.** A 4 000-token prompt
@@ -110,7 +192,7 @@ Two consequences that drive nearly every design rule in this document:
110
192
  a number.
111
193
  - Numbers come from tools; the model narrates, it does not compute.
112
194
  - The failure mode of every instruction is stated ("if X is absent, say so and
113
- stop") — an unstated failure mode is an invented one.
195
+ stop") - an unstated failure mode is an invented one.
114
196
 
115
197
  ---
116
198
 
@@ -120,14 +202,16 @@ Two consequences that drive nearly every design rule in this document:
120
202
 
121
203
  ```
122
204
  my-project/
123
- ├── .github/ the editor's brief — §2.4
205
+ ├── .github/ the editor's brief - §2.4
124
206
  │ ├── copilot-instructions.md this file
125
207
  │ ├── prompts/*.prompt.md tasks you invoke by name
126
208
  │ └── skills/*/SKILL.md reference the editor loads on demand
127
- ├── .env credentials — NEVER committed
128
- ├── INSTRUCTIONS.md house rules, prepended to every agent
209
+ ├── .env this project's environment - NEVER committed
210
+ ├── .spec-sync/ what the last /sync-with-spec applied - §0.1
129
211
  ├── agents.yaml who exists, what they may reach for
130
212
  ├── agents/
213
+ │ ├── instructions.md house rules, prepended to every agent
214
+ │ ├── memory-instructions.md more of them - any `<topic>-instructions.md`
131
215
  │ ├── prompts/
132
216
  │ │ ├── intake.md one agent's own brief
133
217
  │ │ └── adjuster.md
@@ -139,10 +223,10 @@ my-project/
139
223
  │ │ └── SKILL.md
140
224
  │ └── refund_policy/
141
225
  │ ├── SKILL.md
142
- │ └── scripts/calculate.py run at /skills/refund_policy/scripts — §3.4.1
226
+ │ └── scripts/calculate.py run at /skills/refund_policy/scripts - §3.4.1
143
227
  ├── assets/ reference material, read-only at /assets
144
- ├── memory/ the graph agents remember into — §5.3
145
- ├── sandbox/Dockerfile the image commands run in — §3.7
228
+ ├── memory/ the graph agents remember into - §5.3
229
+ ├── sandbox/Dockerfile the image commands run in - §3.7
146
230
  └── sessions/ run state, whatever the agent wrote
147
231
  ```
148
232
 
@@ -150,28 +234,33 @@ Only `agents.yaml` is required, and only `agents:` is required inside it.
150
234
  The config is found by name, in order: `agents.yaml`, `agents.yml`,
151
235
  `agents/agents.yaml`, `agents/agents.yml`.
152
236
 
237
+ The house rules are `agents/instructions.md` plus every
238
+ `agents/<topic>-instructions.md`, read in filename order and prepended to every
239
+ agent (§3.2). A project written before the move keeps its `INSTRUCTIONS.md` in
240
+ the root; that still loads, and `zen check` says to move it.
241
+
153
242
  ### 2.2 Variants
154
243
 
155
- **Single agent, knowledge-heavy** — the most under-used shape. One agent, one
244
+ **Single agent, knowledge-heavy** - the most under-used shape. One agent, one
156
245
  prompt, a large skill catalog. Prefer this until routing is genuinely needed.
157
246
 
158
247
  ```
159
- INSTRUCTIONS.md · agents.yaml · agents/prompts/assistant.md · agents/skills/** (20 skills)
248
+ agents.yaml · agents/instructions.md · agents/prompts/assistant.md · agents/skills/** (20 skills)
160
249
  ```
161
250
 
162
- **Router + specialists** — a cheap intake agent that classifies and hands off.
251
+ **Router + specialists** - a cheap intake agent that classifies and hands off.
163
252
 
164
253
  ```
165
254
  agents/prompts/{intake,billing,technical,escalation}.md
166
255
  ```
167
256
 
168
- **Pipeline** — fixed stages, each handing to the next; the last one answers.
257
+ **Pipeline** - fixed stages, each handing to the next; the last one answers.
169
258
 
170
259
  ```
171
260
  research → draft → review
172
261
  ```
173
262
 
174
- **Shared skills across projects** — `skills:` accepts a list, merged into one
263
+ **Shared skills across projects** - `skills:` accepts a list, merged into one
175
264
  catalog:
176
265
 
177
266
  ```yaml
@@ -181,7 +270,7 @@ skills:
181
270
  ```
182
271
 
183
272
  Paths may not escape the project root unless the root is set to the common
184
- ancestor. Several directories become **one** provider (id `project`) — "which
273
+ ancestor. Several directories become **one** provider (id `project`) - "which
185
274
  folder is this skill in?" is not a question an author should have to answer.
186
275
 
187
276
  ### 2.3 Naming rules (enforced)
@@ -201,29 +290,30 @@ dots.
201
290
  There are two sets of instructions in this repository and they are not for the
202
291
  same reader. Keeping them apart is the single easiest thing to get wrong.
203
292
 
204
- | Tree | Read by | About |
205
- | ---------------------------- | ------------------------------------------ | ----------------------------------- |
206
- | `INSTRUCTIONS.md`, `agents/` | the **project's** agents, at run time | the domain this system works in |
207
- | `.github/` | the **editor's** assistant, while you edit | how a project of this kind is built |
293
+ | Tree | Read by | About |
294
+ | ------------------------ | ------------------------------------------ | ----------------------------------- |
295
+ | `agents.yaml`, `agents/` | the **project's** agents, at run time | the domain this system works in |
296
+ | `.github/` | the **editor's** assistant, while you edit | how a project of this kind is built |
208
297
 
209
298
  The `.github/` tree follows the same progressive-disclosure discipline the
210
- agents do, for the same reason — it is a prefix somebody pays for:
299
+ agents do, for the same reason - it is a prefix somebody pays for:
211
300
 
212
301
  - **`copilot-instructions.md`** is always on. Everything in it is loaded for
213
302
  every request in this folder, so it holds only what is true of every task.
214
303
  - **`.github/skills/<name>/SKILL.md`** is reference the editor loads when its
215
- `description` matches what you asked. Put long, occasional material here —
216
- a command surface, a vendor's quirks, a format spec — not in the file above.
304
+ `description` matches what you asked. Put long, occasional material here -
305
+ a command surface, a vendor's quirks, a format spec - not in the file above.
217
306
  The `description` is the routing key; §3.4 applies to these as much as to the
218
307
  project's own skills.
219
308
  - **`.github/prompts/<name>.prompt.md`** is a task you invoke by name (`/name`),
220
309
  with `mode: agent` and a `description` in its frontmatter. Write one when a
221
- job is done repeatedly and has a right order — adding an agent, adding a
310
+ job is done repeatedly and has a right order - adding an agent, adding a
222
311
  skill, reviewing before a commit.
223
312
 
224
313
  `zen init` and `zen open` rewrite this whole tree from the version of `zen` in
225
314
  hand, so **edits inside `.github/` do not survive**. Project-specific conventions
226
- belong in `INSTRUCTIONS.md` and the agent prompts, which are never overwritten.
315
+ belong in `agents/instructions.md` and the agent prompts, which are never
316
+ overwritten.
227
317
 
228
318
  ### 2.5 What a running agent can actually see
229
319
 
@@ -232,24 +322,24 @@ it. Its entire world is the tools it was granted (§3.5) and four paths:
232
322
 
233
323
  | Path | Is | Access |
234
324
  | ------------ | ----------------------------------------- | ------------------------------------------------------ |
235
- | `/workspace` | the session's workspace — the work itself | read/write; `zen run --read-only` withholds the writes |
236
- | `/assets` | `assets/`, reference material — §3.11 | read-only |
237
- | `/skills` | the whole skill catalog — §3.4.1 | read-only |
238
- | `/memory` | files carried by remembered nodes — §5.3 | read-only, and only when `memory:` is configured |
325
+ | `/workspace` | the session's workspace - the work itself | read/write; `zen run --read-only` withholds the writes |
326
+ | `/assets` | `assets/`, reference material - §3.11 | read-only |
327
+ | `/skills` | the whole skill catalog - §3.4.1 | read-only |
328
+ | `/memory` | files carried by remembered nodes - §5.3 | read-only, and only when `memory:` is configured |
239
329
 
240
330
  Commands run inside the container `sandbox:` describes (§3.7): the same
241
331
  `/workspace`, and whatever the image ships.
242
332
 
243
- Everything else in the project directory is **invisible at run time** —
244
- `agents.yaml`, `INSTRUCTIONS.md`, `agents/prompts/`, `agents/skills/` under that
245
- name, `sandbox/Dockerfile`, `.env`, `.github/`, `sessions/`, and the project
246
- root itself. `zen` reads them to build the run; they are never a path an agent
247
- can open.
333
+ Everything else in the project directory is **invisible at run time** -
334
+ `agents.yaml`, `agents/instructions.md`, `agents/prompts/`, `agents/skills/`
335
+ under that name, `sandbox/Dockerfile`, `.env`, `.github/`, `sessions/`, and the
336
+ project root itself. `zen` reads them to build the run; they are never a path an
337
+ agent can open.
248
338
 
249
339
  Two consequences:
250
340
 
251
341
  - **The workspace is not the project.** It defaults to the session's own empty
252
- folder, and is usually some other repository entirely — `--workspace`, or the
342
+ folder, and is usually some other repository entirely - `--workspace`, or the
253
343
  directory the user was standing in. A prompt saying "read `agents.yaml`" or
254
344
  "the rules are in `agents/skills/`" names a file that is not there.
255
345
  - **Write the mount path, never the repository path.**
@@ -259,46 +349,50 @@ Two consequences:
259
349
  the one place the file is not.
260
350
 
261
351
  An agent that must know something about its own configuration is **told it in
262
- prose**, in the prompt — never sent to read the file that configured it.
352
+ prose**, in the prompt - never sent to read the file that configured it.
263
353
 
264
- #### Checklist — no path an agent cannot open
354
+ #### Checklist - no path an agent cannot open
265
355
 
266
- Run this over `INSTRUCTIONS.md`, every `agents/prompts/*.md` and every
267
- `agents/skills/*/SKILL.md` after editing any of them. Every path-shaped token in
268
- those files is read by a model as an instruction to open something:
356
+ Run this over every `agents/*instructions.md`, every `agents/prompts/*.md` and
357
+ every `agents/skills/*/SKILL.md` after editing any of them. Every path-shaped
358
+ token in those files is read by a model as an instruction to open something:
269
359
 
270
360
  - [ ] Every absolute path begins `/workspace`, `/assets`, `/skills` or
271
361
  `/memory`. Nothing else exists.
272
- - [ ] No project file is named: `agents.yaml`, `INSTRUCTIONS.md`,
362
+ - [ ] No project file is named: `agents.yaml`, `agents/instructions.md`,
273
363
  `agents/prompts/…`, `agents/skills/…`, `sandbox/Dockerfile`, `.env`,
274
364
  `.github/…`, `sessions/…`, `SPECIFICATION.md`, `README.md`, `docs/…`.
275
365
  - [ ] Nothing escapes a mount: no `../`, no `~`, no host path (`/Users/…`,
276
366
  `/home/<someone>`), no `/etc`, `/var` or `/tmp` expected to hold anything
277
367
  the project put there.
278
368
  - [ ] Each `/assets/…` and `/skills/<name>/…` path names a file that is actually
279
- committed there — `zen check` lists what every skill folder ships (§3.4.1).
369
+ committed there - `zen check` lists what every skill folder ships (§3.4.1).
280
370
  - [ ] A skill referring to its own sibling file writes the full
281
371
  `/skills/<name>/` prefix, not `./rates.csv`: the body is rendered into a
282
372
  prompt, not executed from its folder.
283
373
  - [ ] `/memory` is named only in prompts for agents that have `memory:` (§5.3),
284
374
  and `/assets` only where an `assets/` directory exists.
285
- - [ ] The agent holding the prompt has the tool the path implies — `read_file`
375
+ - [ ] The agent holding the prompt has the tool the path implies - `read_file`
286
376
  to open one, `sandbox:*` to run one (§3.4.1).
287
377
  - [ ] Every instruction that opens a file states what to do when it is not there
288
378
  (§4.2, rule 6). Workspaces differ between runs; absence is normal.
289
379
  - [ ] Relative paths appear only where `/workspace`-relative is what was meant.
290
380
 
291
- Finding the candidates, from the project root — every path-shaped token in the
381
+ Finding the candidates, from the project root - every path-shaped token in the
292
382
  three trees, minus the ones under a mount:
293
383
 
294
384
  ```sh
295
- grep -Eohr '[~.]?/?[A-Za-z0-9_.-]*/[A-Za-z0-9_.*-]+' \
296
- INSTRUCTIONS.md agents/prompts agents/skills \
297
- | sort -u | grep -Ev '^/(workspace|assets|skills|memory)(/|$)'
385
+ .github/skills/zen-review/scripts/check-paths.sh
298
386
  ```
299
387
 
300
- Everything it prints is either a false positive (a url, a `path/to` in a
301
- sentence, `and/or`) or a path the agent cannot open. There is no third case.
388
+ It prints each candidate with the file and line it is on, and exits non-zero
389
+ while any remain. Everything it prints is either a path the agent cannot open or
390
+ noise the script has not learned yet - a url, a `path/to` in a sentence, an
391
+ `and/or` - and there is no third case. It strips the noise it does know about,
392
+ so a candidate that survives is one to explain or fix rather than one to skim
393
+ past. `check-paths.sh raw` is the sweep with nothing filtered, for when the
394
+ filter itself is the suspect. The `zen-review` skill says what it covers and
395
+ what it cannot.
302
396
 
303
397
  ---
304
398
 
@@ -307,20 +401,20 @@ sentence, `and/or`) or a path the agent cannot open. There is no third case.
307
401
  ### 3.1 `agents.yaml`
308
402
 
309
403
  The schema is **strict**: an unknown key is a load error, not a value silently
310
- ignored. Errors name the path — `agents.yaml: agents[1].skills.discovery — …`.
404
+ ignored. Errors name the path - `agents.yaml: agents[1].skills.discovery - …`.
311
405
 
312
406
  ```yaml
313
407
  version: 1 # schema version, defaults to 1
314
408
  default: intake # entry agent; wins over any `default: true`
315
409
 
316
410
  providers: {} # named connections (credentials + endpoint)
317
- provider: openai # the provider an unprefixed model id belongs to — §7.3
411
+ provider: openai # the provider an unprefixed model id belongs to - §7.3
318
412
  models: {} # named model configurations
319
413
  model: fast # fallback for agents that do not pin their own
320
- embeddings: {} # named vectorisers — §3.1.1
414
+ embeddings: {} # named vectorisers - §3.1.1
321
415
  embedding: small # the one `AgentProject.embedder()` returns when asked for no name
322
416
  skills: agents/skills # one directory, or a list
323
- assets: assets # reference material, read-only at /assets — §3.11
417
+ assets: assets # reference material, read-only at /assets - §3.11
324
418
 
325
419
  agents: # the only required key; at least one entry
326
420
  - name: intake
@@ -339,13 +433,13 @@ agents: # the only required key; at least one entry
339
433
  | `description` | What a sibling's `transfer_to_<name>` tool tells the model. Write it _for the model_ |
340
434
  | `system` | Prompt path, relative to root. Defaults to `agents/prompts/<name>.md` if present |
341
435
  | `model` | A `models:` alias or shorthand. Falls back to top-level `model:` |
342
- | `tools` | Selectors over the tools `zen run` provides — see §3.6 |
436
+ | `tools` | Selectors over the tools `zen run` provides - see §3.6 |
343
437
  | `handoffs` | Agent names this one may transfer to. Bare strings; no per-edge config |
344
- | `skills` | Skill binding — see §5.2 |
345
- | `fork` | `true`, or `{ agents, maxBranches }` — opt-in to parallel branches; see §6.4 |
438
+ | `skills` | Skill binding - see §5.2 |
439
+ | `fork` | `true`, or `{ agents, maxBranches }` - opt-in to parallel branches; see §6.4 |
346
440
  | `default` | `true` marks the entry point when no top-level `default:` |
347
441
 
348
- **Providers** — a provider is a _connection_, not a model. One client is built
442
+ **Providers** - a provider is a _connection_, not a model. One client is built
349
443
  per name and shared, so five agents on one key open one connection pool.
350
444
 
351
445
  | Field | Meaning |
@@ -353,20 +447,20 @@ per name and shared, so five agents on one key open one connection pool.
353
447
  | `kind` | `openai` \| `google` \| `vertex` \| `anthropic` \| `openrouter` \| `openai-compatible` (default `openai`) |
354
448
  | `apiKey` / `apiKeyEnv` | Literal, `${VAR}`, or the name of the env var holding it |
355
449
  | `baseURL` / `baseURLEnv` | For gateways and compatible endpoints |
356
- | `project` / `location` | **vertex only** — GCP project id and region (or `global`) |
450
+ | `project` / `location` | **vertex only** - GCP project id and region (or `global`) |
357
451
  | `headers` | Sent on every request: routing, attribution, api versions |
358
452
  | `timeoutMs`, `maxRetries` | Per-request timeout and retry count |
359
453
 
360
454
  `openai`, `google`, `vertex`, `anthropic`, `openrouter` and `openai-compatible`
361
455
  are usable as provider _names_ with no declaration at all. Declare a
362
- `providers:` entry only when it says something the default does not — a second
456
+ `providers:` entry only when it says something the default does not - a second
363
457
  key, a region, a base url.
364
458
 
365
459
  `${VAR}` and `${VAR:-fallback}` expand from the environment, compose inside
366
460
  longer values (`https://${GATEWAY}/v1`), and are **lazy**: a declared-but-unused
367
461
  provider with a missing key does not fail loading.
368
462
 
369
- **Errors caught at load** — rely on these instead of defensive checks:
463
+ **Errors caught at load** - rely on these instead of defensive checks:
370
464
 
371
465
  - any unknown key; any name breaking the pattern
372
466
  - `models.<alias>.provider` naming an undeclared provider
@@ -375,27 +469,27 @@ provider with a missing key does not fail loading.
375
469
  - `agents[].handoffs` naming an unknown agent, or the agent itself
376
470
  - `agents[].skills.provider` / `.allow` / `.preload` naming something absent
377
471
  - a `preload:` entry missing from `allow:`
378
- - `agents[].fork.agents` naming an unknown agent, or being empty; `maxBranches` below 2
472
+ - `agents[].fork.agents` naming an unknown agent, or being empty; `maxBranches` below 1
379
473
  - `system:` pointing at a missing file, or outside the project root
380
474
 
381
- **Not caught at load** — a model id whose prefix is missing and so resolves to the
475
+ **Not caught at load** - a model id whose prefix is missing and so resolves to the
382
476
  wrong provider (§7.3), and any combination of knobs the vendor rejects at request
383
477
  time, such as OpenAI reasoning on chat completions (§7.6). Both surface on the
384
478
  first call, so read §7 before writing a `models:` entry.
385
479
 
386
- **Caught by `zen check`, not by the loader** — a handoff with no path back
480
+ **Caught by `zen check`, not by the loader** - a handoff with no path back
387
481
  (`handoff.one-way`, §6.3). The project runs; it just cannot return.
388
482
 
389
- **Comments in `agents.yaml`** — this file is the architecture diagram of the
483
+ **Comments in `agents.yaml`** - this file is the architecture diagram of the
390
484
  project, and its comments are read by whoever has to change it next. Write them
391
485
  at that level:
392
486
 
393
487
  - Say **what a block is for** and **why it exists**: what this agent owns, why
394
488
  this one is on the deep tier, why this handoff edge is there.
395
- - Keep them **short** — one line above a block, a few words at the end of a line.
489
+ - Keep them **short** - one line above a block, a few words at the end of a line.
396
490
  A comment longer than the thing it describes is a design doc in the wrong file.
397
491
  - Do **not** restate the runtime. How skills are discovered, how the trajectory
398
- is appended, how prompt caching works, what the loader validates — none of that
492
+ is appended, how prompt caching works, what the loader validates - none of that
399
493
  belongs here. It is documented in this file and in `docs/agents-yaml.md`.
400
494
  - Do **not** restate the key. `# the model this agent uses` above `model:` is
401
495
  noise; `# cheap: it only classifies` is not.
@@ -404,7 +498,7 @@ at that level:
404
498
  ```yaml
405
499
  # Intake classifies and routes; it never answers.
406
500
  - name: intake
407
- model: router # cheap tier — one sentence in, one handoff out
501
+ model: router # cheap tier - one sentence in, one handoff out
408
502
  handoffs: [adjuster, escalation]
409
503
  ```
410
504
 
@@ -432,12 +526,12 @@ embedding: small
432
526
  | Field | Meaning |
433
527
  | ------------------------ | ------------------------------------------------------------------------ |
434
528
  | `provider` | A `providers:` name or a built-in kind. Defaults to the default provider |
435
- | `model` | **Required.** The bare id — the object form never re-parses a shorthand |
529
+ | `model` | **Required.** The bare id - the object form never re-parses a shorthand |
436
530
  | `dimensions` | Truncate to this width, where the model supports it |
437
531
  | `apiKey` / `baseURL` / … | The same credential fields a provider takes, for a one-off connection |
438
- | `title` | **gemini only** — a document title the retrieval task type weighs |
439
- | `maxBatch` | **gemini only** — texts per request; see below |
440
- | `routing` | **openrouter only** — which upstream provider serves the request |
532
+ | `title` | **gemini only** - a document title the retrieval task type weighs |
533
+ | `maxBatch` | **gemini only** - texts per request; see below |
534
+ | `routing` | **openrouter only** - which upstream provider serves the request |
441
535
 
442
536
  Four things differ from `models:` and are worth knowing before you write one:
443
537
 
@@ -445,14 +539,14 @@ Four things differ from `models:` and are worth knowing before you write one:
445
539
  api means nothing on any protocol. The shorthand is `[provider:]model`.
446
540
  - **`kind: anthropic` has no embeddings API at all.** Anthropic publishes none
447
541
  and points at third parties; an `embeddings:` entry on an Anthropic provider
448
- fails at load. Use another provider — the connection need not be the one the
542
+ fails at load. Use another provider - the connection need not be the one the
449
543
  agents talk through.
450
544
  - **Not a per-agent key.** Nothing in the runtime consumes a vectoriser yet, so
451
545
  there is no `agents[].embedding:`. A TypeScript host reaches one with
452
546
  `project.embedder()` for the default, or `project.embedder('large')` by name.
453
547
  - **Vectors come back unit length**, so cosine and dot product agree. This is a
454
548
  guarantee of the runtime, not of the vendor: truncating with `dimensions:` is a
455
- raw slice and only some models rescale afterwards — `gemini-embedding-2` does,
549
+ raw slice and only some models rescale afterwards - `gemini-embedding-2` does,
456
550
  `gemini-embedding-001` returns |v| ≈ 0.58 at 768 of its 3072. Pass
457
551
  `normalize: false` on a call to see what the model actually said.
458
552
 
@@ -464,14 +558,26 @@ hide that. `maxBatch` therefore defaults to `1`; raise it only for a
464
558
  `zen check` reports every declared embedding beside the models, with the
465
559
  credential each one would need.
466
560
 
467
- ### 3.2 `INSTRUCTIONS.md`
561
+ ### 3.2 `agents/instructions.md` and friends
468
562
 
469
- House rules, read **once** and prepended to every agent's system prompt. It is
470
- the stable head of the cached prefix, so it should change rarely. The name is
471
- deliberately not `AGENTS.md` — that one belongs to the coding assistant reading
563
+ House rules, read **once** and prepended to every agent's system prompt. They are
564
+ the stable head of the cached prefix, so they should change rarely. The name is
565
+ deliberately not `AGENTS.md` - that one belongs to the coding assistant reading
472
566
  this file, and these rules address the project's own agents.
473
567
 
474
- Put here only what is true for **every** agent:
568
+ There may be more than one. `agents/instructions.md` is the base; every
569
+ `agents/<topic>-instructions.md` is read after it, in filename order, and every
570
+ agent gets all of them. Each arrives as its own block tagged with the file it
571
+ came from - `<house_rules src="agents/memory-instructions.md">` - so the model
572
+ can tell one document from the next.
573
+
574
+ Split a topic out when it is true for every agent but belongs to **one
575
+ capability**: the memory usage rules (§5.3) are the standard case - they arrive
576
+ when `memory:` is turned on and are deleted when it is turned off, which is a
577
+ whole file rather than a section somebody has to find. Do not split by author,
578
+ by date, or to keep a file short; that is what a skill is for.
579
+
580
+ Put in them only what is true for **every** agent:
475
581
 
476
582
  - identity and domain ("You work the property claims desk")
477
583
  - non-negotiable prohibitions (regulatory, legal, safety)
@@ -482,11 +588,11 @@ Do **not** put here: anything one agent needs and another does not; anything tha
482
588
  changes weekly; long reference data (that is a skill).
483
589
 
484
590
  **When writing it, remember what it is: a prefix on every agent's system prompt.**
485
- It is not a README and not a design document — every line is paid for on every
591
+ It is not a README and not a design document - every line is paid for on every
486
592
  call, by every agent, and each one is an instruction the model will try to
487
593
  follow. So:
488
594
 
489
- - It is a **prompt**: §4.2 applies in full — second person, imperative, no
595
+ - It is a **prompt**: §4.2 applies in full - second person, imperative, no
490
596
  hedging, no meta-talk about the runtime, failure paths stated.
491
597
  - Carry the **shared model of the project**: what this system is, what the agents
492
598
  are collectively for, how the work flows between them, and the vocabulary and
@@ -498,13 +604,13 @@ follow. So:
498
604
  - Anything only one agent needs goes in that agent's prompt instead. If you find
499
605
  yourself writing "if you are the router…", you are in the wrong file.
500
606
 
501
- Target 20–60 lines. If it exceeds ~100, split the stable half out into a
607
+ Target 20–60 lines each. If one exceeds ~100, split the stable half out into a
502
608
  preloaded skill.
503
609
 
504
610
  ### 3.3 `agents/prompts/<name>.md`
505
611
 
506
612
  Plain Markdown, no frontmatter. This is the agent's _job description_, appended
507
- after `INSTRUCTIONS.md`.
613
+ after the house rules.
508
614
 
509
615
  Structure that works:
510
616
 
@@ -513,7 +619,7 @@ Structure that works:
513
619
 
514
620
  <what it does, as a short numbered procedure or 3–5 rules>
515
621
 
516
- <what it must NOT do — especially the neighbouring agent's job>
622
+ <what it must NOT do - especially the neighbouring agent's job>
517
623
 
518
624
  <how to finish: the shape of the answer, or which handoff ends the turn>
519
625
  ```
@@ -530,7 +636,7 @@ Your job is small and you should finish it fast:
530
636
  and see what it covers.
531
637
  3. Hand the case to the `adjuster` agent, which owns the peril policies.
532
638
 
533
- Do not quote coverage rules yourself — you do not have them. Do not ask the
639
+ Do not quote coverage rules yourself - you do not have them. Do not ask the
534
640
  claimant for anything the message already contains.
535
641
  ```
536
642
 
@@ -538,9 +644,9 @@ Note what makes it work: it is 9 lines; it names the tool and the handoff
538
644
  literally; it states the boundary ("you do not have them") with the _reason_; it
539
645
  forbids the specific failure that agent actually exhibits.
540
646
 
541
- Write it as a prompt, under the rules in §4.2 — **instructive and concise**. It
542
- tells one agent what to do; it never explains the system (`INSTRUCTIONS.md`
543
- already did, §3.2), never repeats a house rule, and never describes the runtime.
647
+ Write it as a prompt, under the rules in §4.2 - **instructive and concise**. It
648
+ tells one agent what to do; it never explains the system (the house rules
649
+ already did, §3.2), never repeats one of them, and never describes the runtime.
544
650
  Every line should be an instruction the model can act on or a boundary it can
545
651
  check itself against.
546
652
 
@@ -549,8 +655,8 @@ yet.
549
655
 
550
656
  ### 3.4 Skills
551
657
 
552
- A skill is curated, reusable instruction content — plus the files and tools it
553
- needs — loaded **on demand** instead of permanently occupying the system prompt.
658
+ A skill is curated, reusable instruction content - plus the files and tools it
659
+ needs - loaded **on demand** instead of permanently occupying the system prompt.
554
660
  One layout, and only one:
555
661
 
556
662
  ```
@@ -564,7 +670,7 @@ to move it to `refund_policy/SKILL.md`. A skill with nowhere to put a table, an
564
670
  example or a script can only ever be prose, which is the half of the idea that
565
671
  does not need a file.
566
672
 
567
- Frontmatter is a deliberately small subset of YAML — `key: value`, plus `[a, b]`
673
+ Frontmatter is a deliberately small subset of YAML - `key: value`, plus `[a, b]`
568
674
  flow lists for `tags` and `tools`. **Every key is optional**: `name` defaults to
569
675
  the folder name, `description` to the first non-empty line of the body.
570
676
 
@@ -579,9 +685,9 @@ tools: [issue_refund]
579
685
 
580
686
  A parcel counts as late once it passes its promised delivery date by 48 hours.
581
687
 
582
- - **48h to 7 days late** — apologise, confirm the parcel is still moving, and
688
+ - **48h to 7 days late** - apologise, confirm the parcel is still moving, and
583
689
  refund the shipping fee. Do not refund the goods.
584
- - **More than 7 days late** — treat the parcel as lost. Offer a replacement at
690
+ - **More than 7 days late** - treat the parcel as lost. Offer a replacement at
585
691
  no cost, or a full refund, whichever the customer prefers.
586
692
 
587
693
  Never give a new delivery date. Say "still in transit" instead.
@@ -594,7 +700,7 @@ skill is needed_, not as a title:
594
700
  | Bad | Good |
595
701
  | --------------------------- | ------------------------------------------------------------------------------------ |
596
702
  | `description: Refunds` | `description: When a refund is owed, and how much.` |
597
- | `description: Water policy` | `description: Escape of water from plumbing and tanks — and the freezing exclusion.` |
703
+ | `description: Water policy` | `description: Escape of water from plumbing and tanks - and the freezing exclusion.` |
598
704
 
599
705
  **`tools:` in frontmatter** names skill-owned tools. They are declared to the
600
706
  provider from turn 0 (the schema never changes) but **refuse to execute** until
@@ -635,7 +741,7 @@ Rules that follow from the mount:
635
741
  - The agent needs `sandbox:*` to run the script and `read_file` to open a data
636
742
  file. Grant them, or the skill's own instructions cannot be followed (§3.5).
637
743
  - **The interpreter must already be in the sandbox image.** A skill whose
638
- script needs `pandas` is a `sandbox/Dockerfile` line that was never written —
744
+ script needs `pandas` is a `sandbox/Dockerfile` line that was never written -
639
745
  not a first line that runs `pip install` on every turn (§3.7).
640
746
  - **`/skills` is read-only.** A script that produces a file must take an output
641
747
  path and write it under `/workspace`.
@@ -644,7 +750,7 @@ Rules that follow from the mount:
644
750
  directory is `/workspace`.
645
751
  - Say what to do when the script fails, as with any other instruction (§4.2).
646
752
  - With several `skills:` directories, one catalog is the whole of `/skills` and
647
- several take `/skills/<folder>` each — check the rendered path before writing
753
+ several take `/skills/<folder>` each - check the rendered path before writing
648
754
  it into the body.
649
755
  - The mount is created before anything is loaded, so `allow:` limits what an
650
756
  agent can **load**, not what it can **read**. Do not put anything in the
@@ -656,8 +762,8 @@ committed shows up as a skill with no files.
656
762
  ### 3.5 Tools
657
763
 
658
764
  A tool is what an agent can _do_ rather than say. `zen run` provides three
659
- groups — the workspace tools (§3.6), the sandbox tools (§3.7) and the web tools
660
- (§3.8) — and `agents.yaml` decides which agent holds which. Nothing else reaches
765
+ groups - the workspace tools (§3.6), the sandbox tools (§3.7) and the web tools
766
+ (§3.8) - and `agents.yaml` decides which agent holds which. Nothing else reaches
661
767
  the machine, so `tools:` is the whole permission model: an agent that does not
662
768
  name a tool cannot use it, whatever its prompt says.
663
769
 
@@ -668,7 +774,7 @@ Two rules follow:
668
774
  - **Say in the prompt when to reach for what.** A granted tool the prompt never
669
775
  mentions is used at the model's discretion, which is not the same as never.
670
776
 
671
- Skills can own tools too — `tools:` in a skill's frontmatter (§3.4) names tools
777
+ Skills can own tools too - `tools:` in a skill's frontmatter (§3.4) names tools
672
778
  that refuse to run until that skill is active.
673
779
 
674
780
  ### 3.6 The workspace tools (`workspace:*`)
@@ -687,12 +793,12 @@ them cannot see a file at all.
687
793
  | `move_file` | Moves or renames; refuses to clobber without `overwrite` |
688
794
  | `delete_file` | Deletes; a directory needs `recursive` |
689
795
 
690
- Every path is resolved through one containment gate — symlinks followed, then
691
- checked — so nothing outside the workspace root is reachable. Reads are capped
796
+ Every path is resolved through one containment gate - symlinks followed, then
797
+ checked - so nothing outside the workspace root is reachable. Reads are capped
692
798
  and listings bounded, so no single call can flood the context.
693
799
 
694
- Paths are written relative to the root, and `/workspace/...` — the name the same
695
- directory has inside the sandbox (§3.7) — is accepted as well, so a path copied
800
+ Paths are written relative to the root, and `/workspace/...` - the name the same
801
+ directory has inside the sandbox (§3.7) - is accepted as well, so a path copied
696
802
  out of a command's output does not have to be translated first. When a mount is
697
803
  configured the tools also _report_ that name, so the two toolsets speak one
698
804
  vocabulary and a path can be passed from either to the other unchanged; with no
@@ -720,7 +826,7 @@ agents:
720
826
 
721
827
  Selectors apply in the order written, so a `-` line reads as an exception to the
722
828
  line above it. Quote a lone `'*'`: unquoted, YAML reads it as an alias and
723
- refuses the file. `workspace:*` needs no quoting. There is no name globbing —
829
+ refuses the file. `workspace:*` needs no quoting. There is no name globbing -
724
830
  `read_*` is an unknown tool, because a selector should track a declared set, not
725
831
  a naming habit.
726
832
 
@@ -730,7 +836,7 @@ asks for: the deployment overriding the repository, as everywhere else.
730
836
  **Prompting for them.** Three lines earn their place in any prompt that grants
731
837
  this group:
732
838
 
733
- - Read before editing — `apply_patch` matches on exact text, so a patch built
839
+ - Read before editing - `apply_patch` matches on exact text, so a patch built
734
840
  from memory fails.
735
841
  - Prefer `apply_patch` to `write_file` for an existing file. Rewriting a file to
736
842
  change one line costs the whole file in output tokens and loses everything the
@@ -751,7 +857,7 @@ build, test, install and inspect rather than only read and write.
751
857
  | `read_command_output` | A window of a job's output from `start_line`, plus whether it still runs |
752
858
  | `stop_command` | Signals a job's process group |
753
859
 
754
- The container is the boundary. Nothing inspects the command — there is no
860
+ The container is the boundary. Nothing inspects the command - there is no
755
861
  allow-list of binaries and no pattern matching on what the model wrote, because
756
862
  both are trivially defeated and neither survives a shell. What holds is that
757
863
  only the workspace and the session's `/home/agent` are mounted, the container
@@ -759,12 +865,12 @@ is removed at the end of the session, and the command is never a shell argument
759
865
  on the host: it travels on stdin to `/bin/sh` inside.
760
866
 
761
867
  **Configuring it.** A top-level `sandbox:` block in `agents.yaml` describes the
762
- container. Every field has a default, so the block is optional — write only the
868
+ container. Every field has a default, so the block is optional - write only the
763
869
  lines that differ:
764
870
 
765
871
  ```yaml
766
872
  sandbox:
767
- persist: true # recommended — see below
873
+ persist: true # recommended - see below
768
874
  image: docker.io/library/python:3.14-slim-bookworm # the default
769
875
  cpus: 4 # fractional cores
770
876
  memory: 4096 # MiB
@@ -773,25 +879,36 @@ sandbox:
773
879
  env: [HTTPS_PROXY, NO_PROXY] # host variables to forward, by NAME
774
880
  ```
775
881
 
776
- | Field | Default | Meaning |
777
- | --------- | --------------------------------------------- | -------------------------------------------- |
778
- | `image` | `docker.io/library/python:3.14-slim-bookworm` | The base image commands run in |
779
- | `build` | none | A Dockerfile to build instead |
780
- | `cpus` | the host's | Fractional cores |
781
- | `memory` | the host's | MiB |
782
- | `network` | `bridge` | `bridge` / `none` / `host` |
783
- | `workdir` | `/workspace` | Mount point and default cwd |
784
- | `timeout` | `120` | Seconds per command |
785
- | `user` | the image's | uid, name, or `uid:gid` |
786
- | `persist` | `false` — **set it to `true`** | Keep the container between runs of a session |
787
- | `env` | none | Host variables to forward, **names** |
788
-
789
- `env:` takes **names, never values** — a value here would be a secret in the
790
- repository — and anything credential-shaped (`KEY`, `TOKEN`, `SECRET`,
882
+ | Field | Default | Meaning |
883
+ | --------- | --------------------------------------------- | --------------------------------------------- |
884
+ | `image` | `docker.io/library/python:3.14-slim-bookworm` | The base image commands run in |
885
+ | `build` | none | A Dockerfile to build instead |
886
+ | `cpus` | the host's | Fractional cores |
887
+ | `memory` | the host's | MiB |
888
+ | `network` | `bridge` | `bridge` / `none` / `host` |
889
+ | `workdir` | `/workspace` | Mount point and default cwd |
890
+ | `timeout` | `120` | Seconds per command |
891
+ | `user` | the image's | uid, name, or `uid:gid` |
892
+ | `persist` | `false` - **set it to `true`** | Keep the container between runs of a session |
893
+ | `env` | none | Host variables to forward, **names** |
894
+ | `keys` | `true` | Whether model credentials reach the container |
895
+
896
+ `env:` takes **names, never values** - a value here would be a secret in the
897
+ repository - and anything credential-shaped (`KEY`, `TOKEN`, `SECRET`,
791
898
  `PASSWORD`, `CREDENTIAL`) is refused at load.
792
899
 
900
+ **Model credentials are already inside the container** (`keys:`, default
901
+ `true`). Every provider key the run holds is forwarded by name, and a Vertex
902
+ service-account file is bind-mounted read-only at `/run/zenera/keys` with
903
+ `GOOGLE_APPLICATION_CREDENTIALS` rewritten to point there - wherever it sits on
904
+ the host, keyring included. So a `zen` command that embeds (`zen rag … search`)
905
+ works in the sandbox: do not assume otherwise, do not route around it, and
906
+ never copy a key file into the project to "reach" the container. Only
907
+ `keys: false` / `zen run --no-keys` withholds them, and `network: none` is what
908
+ stops the call going out.
909
+
793
910
  **`build:`, when no published image fits.** A project that needs two runtimes,
794
- or a pinned toolchain, names a Dockerfile instead of an image — `zen init`
911
+ or a pinned toolchain, names a Dockerfile instead of an image - `zen init`
795
912
  writes one at `sandbox/Dockerfile` with Python and Node in it:
796
913
 
797
914
  ```yaml
@@ -802,7 +919,7 @@ sandbox:
802
919
  # context: . # what the build may COPY from; the Dockerfile's folder by default
803
920
  ```
804
921
 
805
- `image:` and `build:` cannot both be set — a Dockerfile names its own base in
922
+ `image:` and `build:` cannot both be set - a Dockerfile names its own base in
806
923
  its `FROM` line. The tag is a hash of the Dockerfile and its context, so
807
924
  editing it produces a new image and, like any other change here, a new
808
925
  container. `zen check` builds it and runs a command in it, so a Dockerfile that
@@ -834,12 +951,12 @@ agents:
834
951
 
835
952
  **Write `persist: true` unless you have a reason not to.** By default the
836
953
  container is _removed_ when the session closes, and only two paths survive it:
837
- `/workspace`, and `/home/agent` — which is `$HOME` inside, backed by the session
954
+ `/workspace`, and `/home/agent` - which is `$HOME` inside, backed by the session
838
955
  directory. That covers `pip install --user`, `npm config` and `~/.cache`, but it
839
956
  does **not** cover the ordinary thing an agent actually does: `pip install X` or
840
957
  `apt-get install X` as root writes to the container's system paths, and those
841
958
  are gone on the next `zen run`. The agent then reinstalls, silently, every
842
- single time — and usually does not realise it has, because the previous run's
959
+ single time - and usually does not realise it has, because the previous run's
843
960
  transcript says it succeeded.
844
961
 
845
962
  ```yaml
@@ -857,7 +974,7 @@ Changing any field renames the container, so bumping the image gets a fresh one
857
974
  rather than an old one quietly persisting with the wrong contents. That is also
858
975
  the one sharp edge of `persist: true`: a config change abandons the old
859
976
  container with whatever was installed in it, so a long-lived setup still belongs
860
- in `image:` — or in `sandbox/Dockerfile` — rather than in an accumulated rootfs.
977
+ in `image:` - or in `sandbox/Dockerfile` - rather than in an accumulated rootfs.
861
978
 
862
979
  Granting the group is what makes the project need Podman: `zen run` checks the
863
980
  engine before the first turn and exits `5` with an install command if it is
@@ -867,15 +984,15 @@ reference: `docs/agents-yaml.md`.
867
984
  **Prompting for them.** Two lines earn their place:
868
985
 
869
986
  - The workspace is at `/workspace` and is the same directory the file tools
870
- see — an edit made with `apply_patch` is what a command will compile, and a
987
+ see - an edit made with `apply_patch` is what a command will compile, and a
871
988
  path from either side works on both.
872
- - Anything that does not return, returns — use `run_command_background` for a
989
+ - Anything that does not return, returns - use `run_command_background` for a
873
990
  server, not `run_command` with a large timeout.
874
991
 
875
992
  ### 3.8 The web tools (`exa:*`)
876
993
 
877
994
  The third group `zen run` builds. These reach the live web through
878
- [Exa](https://exa.ai) — a search index built for models rather than for people,
995
+ [Exa](https://exa.ai) - a search index built for models rather than for people,
879
996
  so a query is a sentence describing what is wanted, not a bag of keywords.
880
997
 
881
998
  | Tool | What it does |
@@ -886,7 +1003,7 @@ so a query is a sentence describing what is wanted, not a bag of keywords.
886
1003
 
887
1004
  The three are meant to be used in that order: **search to find, read to quote.**
888
1005
  An excerpt is enough to judge which source to trust and never enough to cite
889
- from — `web_search` returns the sentences that made a page match, not the page.
1006
+ from - `web_search` returns the sentences that made a page match, not the page.
890
1007
  `web_answer` runs a search _and_ a model on the other side, so it is the slowest
891
1008
  and dearest of the three; it earns its cost when the answer is a fact spread
892
1009
  over several pages, and wastes it when a specific document is wanted.
@@ -903,7 +1020,7 @@ agents:
903
1020
 
904
1021
  **The key.** All three read `$EXA_API_KEY` **when they are called**, not when the
905
1022
  project loads. So a project naming `exa:*` still loads on a machine that has no
906
- key — the tools simply refuse, on the turn that tried, saying which variable is
1023
+ key - the tools simply refuse, on the turn that tried, saying which variable is
907
1024
  missing. Get a key from <https://dashboard.exa.ai/api-keys> and hold it in
908
1025
  either place:
909
1026
 
@@ -922,14 +1039,14 @@ project is still valid, it just cannot search yet.
922
1039
  | Argument | On | Why it exists |
923
1040
  | ------------------------------------------ | ------------ | ------------------------------------------------------------------------------- |
924
1041
  | `num_results` | `web_search` | 8 by default, 25 at most |
925
- | `include_domains` / `exclude_domains` | `web_search` | The replacement for `site:` — operators in the query text do not work here |
1042
+ | `include_domains` / `exclude_domains` | `web_search` | The replacement for `site:` - operators in the query text do not work here |
926
1043
  | `start_published_date` / `end_published_…` | `web_search` | ISO 8601. The only reliable way to exclude a stale answer |
927
1044
  | `category` | `web_search` | `company`, `publication`, `news`, `personal site`, `financial report`, `people` |
928
1045
  | `max_characters` | `web_read` | 4 000 by default, 10 000 at most; `truncated` says when a page was cut |
929
1046
  | `max_age_hours` | `web_read` | `0` forces a live crawl. Omit it unless the page changes by the hour |
930
1047
 
931
- Every reply is bounded — pages are cut at the cap and the whole call at 128 KiB
932
- of text — so one call cannot flood the context. Each carries `cost_usd`, which
1048
+ Every reply is bounded - pages are cut at the cap and the whole call at 128 KiB
1049
+ of text - so one call cannot flood the context. Each carries `cost_usd`, which
933
1050
  is what the vendor charged for that call.
934
1051
 
935
1052
  A failure is **reported, not raised**: a refused key, an exhausted balance, a
@@ -940,7 +1057,7 @@ did load, so one bad link does not lose the rest.
940
1057
  **Prompting for them.** Three lines earn their place in any prompt granting this
941
1058
  group:
942
1059
 
943
- - Search with a sentence, not keywords — the query is read by a model.
1060
+ - Search with a sentence, not keywords - the query is read by a model.
944
1061
  - Never quote an excerpt. `web_search` says which page to open; `web_read` says
945
1062
  what it contains.
946
1063
  - Say when the web is allowed to override what the model already believes, and
@@ -965,7 +1082,7 @@ zen run --read-only withhold every tool that can write
965
1082
 
966
1083
  The project is read **once, up front**: a missing prompt, an unknown tool name,
967
1084
  a handoff to nobody all fail before the first call, with the offending key
968
- named. Flags always win over the file — the repository states intent, the
1085
+ named. Flags always win over the file - the repository states intent, the
969
1086
  invocation overrides it.
970
1087
 
971
1088
  A session owns a workspace, a trajectory, memory and whatever the agent wrote,
@@ -980,17 +1097,17 @@ GOOGLE_APPLICATION_CREDENTIALS=./.keys/vertex.json
980
1097
  EXA_API_KEY=...
981
1098
  ```
982
1099
 
983
- Never commit. Never inline a key into `agents.yaml` — use `${VAR}`. Never print a
1100
+ Never commit. Never inline a key into `agents.yaml` - use `${VAR}`. Never print a
984
1101
  key in a log line, a test fixture, or a chat message.
985
1102
 
986
1103
  ### 3.11 `assets/`
987
1104
 
988
- `assets/` next to `agents.yaml` — or `assets: <path>` — is reference material
1105
+ `assets/` next to `agents.yaml` - or `assets: <path>` - is reference material
989
1106
  every agent can read and none can write. It is mounted at `/assets`: the file
990
1107
  tools read, list and search it, `run_command` sees it bind-mounted read-only,
991
1108
  and every tool that would change it refuses.
992
1109
 
993
- Put a handbook, a specification, a schema or a style guide there — what agents
1110
+ Put a handbook, a specification, a schema or a style guide there - what agents
994
1111
  consult while working, as opposed to the workspace, which is the work. Do not
995
1112
  point `assets:` at the project root: that hands every agent the `sessions/`
996
1113
  directory, every transcript of every run, including the one reading it.
@@ -1006,13 +1123,14 @@ is a different project, not a different key.
1006
1123
  ### 4.1 Composition order
1007
1124
 
1008
1125
  ```
1009
- 1. INSTRUCTIONS.md (shared, once, all agents)
1010
- 2. agents/prompts/<n>.md (this agent)
1011
- 3. skill index (rendered by the runtime — do not hand-write it)
1012
- 4. preloaded skills (activated before the first call)
1126
+ 1. agents/instructions.md (shared, once, all agents)
1127
+ 2. agents/*-instructions.md (shared too, filename order)
1128
+ 3. agents/prompts/<n>.md (this agent)
1129
+ 4. skill index (rendered by the runtime - do not hand-write it)
1130
+ 5. preloaded skills (activated before the first call)
1013
1131
  ```
1014
1132
 
1015
- Never duplicate `INSTRUCTIONS.md` content into an agent prompt; never hand-render
1133
+ Never duplicate house-rules content into an agent prompt; never hand-render
1016
1134
  a list of skills into a prompt (the runtime does it, and a hand-written one goes
1017
1135
  stale silently).
1018
1136
 
@@ -1022,7 +1140,7 @@ stale silently).
1022
1140
  reference." Not "The agent should be able to look up policies."
1023
1141
  2. **Name things literally.** Tools in backticks by their exact name; agents by
1024
1142
  their exact `name:`. The model matches strings.
1025
- 3. **State the boundary and the reason.** "Do not quote coverage rules — you do
1143
+ 3. **State the boundary and the reason.** "Do not quote coverage rules - you do
1026
1144
  not have them." A prohibition without a reason gets rationalised away.
1027
1145
  4. **Prefer prohibitions that are specific.** "Never say 'approved'" beats "be
1028
1146
  careful about commitments".
@@ -1045,7 +1163,7 @@ stale silently).
1045
1163
 
1046
1164
  Write like a competent colleague briefing a new hire on their first day: short
1047
1165
  sentences, concrete nouns, the reason behind each rule. The model mirrors the
1048
- register it is given — a prompt written in bureaucratic hedging produces
1166
+ register it is given - a prompt written in bureaucratic hedging produces
1049
1167
  bureaucratic hedging.
1050
1168
 
1051
1169
  ### 4.4 Debugging a prompt
@@ -1054,8 +1172,8 @@ When behaviour is wrong, in this order:
1054
1172
 
1055
1173
  1. **Read the actual assembled prompt**, not the file. Use the inspect report.
1056
1174
  2. Is the instruction _present_? (Missed skill load, wrong agent, compaction.)
1057
- 3. Is it _contradicted_ by `INSTRUCTIONS.md` or a skill? Adjacent contradictions
1058
- win over distant ones; later text usually wins over earlier.
1175
+ 3. Is it _contradicted_ by a house-rules file or a skill? Adjacent
1176
+ contradictions win over distant ones; later text usually wins over earlier.
1059
1177
  4. Is it _specific enough to be checkable_? Rewrite as a testable assertion.
1060
1178
  5. Only then consider a stronger model.
1061
1179
 
@@ -1070,15 +1188,15 @@ it forever.
1070
1188
 
1071
1189
  The decision for every piece of knowledge:
1072
1190
 
1073
- | Where | Cost | Use when |
1074
- | ------------------- | ----------------------- | ----------------------------------------------------- |
1075
- | `INSTRUCTIONS.md` | every call, every agent | true always, for everyone |
1076
- | agent prompt | every call, one agent | true always, for this job |
1077
- | **preloaded skill** | every call, one agent | always needed, but versioned/shared separately |
1078
- | **indexed skill** | one line until loaded | needed _sometimes_, model can tell when from one line |
1079
- | **searched skill** | nothing until searched | catalog too large to index (>~30 entries) |
1080
- | **tool** | schema only | needs live data, exact arithmetic, or a side effect |
1081
- | **memory** | recall block when hit | learned across runs, not authored |
1191
+ | Where | Cost | Use when |
1192
+ | ------------------------- | ----------------------- | ----------------------------------------------------- |
1193
+ | `agents/*instructions.md` | every call, every agent | true always, for everyone |
1194
+ | agent prompt | every call, one agent | true always, for this job |
1195
+ | **preloaded skill** | every call, one agent | always needed, but versioned/shared separately |
1196
+ | **indexed skill** | one line until loaded | needed _sometimes_, model can tell when from one line |
1197
+ | **searched skill** | nothing until searched | catalog too large to index (>~30 entries) |
1198
+ | **tool** | schema only | needs live data, exact arithmetic, or a side effect |
1199
+ | **memory** | recall block when hit | learned across runs, not authored |
1082
1200
 
1083
1201
  ### 5.2 Skill bindings
1084
1202
 
@@ -1093,28 +1211,28 @@ agents:
1093
1211
  maxIndexEntries: 20 # cap the rendered index
1094
1212
  ```
1095
1213
 
1096
- - **`index`** — names and descriptions rendered into the system prompt. The
1214
+ - **`index`** - names and descriptions rendered into the system prompt. The
1097
1215
  default and right answer for most catalogs.
1098
- - **`search`** — no index, only a `skill_search` tool. For catalogs too large to
1216
+ - **`search`** - no index, only a `skill_search` tool. For catalogs too large to
1099
1217
  render.
1100
- - **`none`** — preloads only.
1101
- - **`preload`** — for content there is no case for the model to decline: house
1218
+ - **`none`** - preloads only.
1219
+ - **`preload`** - for content there is no case for the model to decline: house
1102
1220
  tone, a formatting contract. Making it choose is a wasted round trip.
1103
1221
  Preloaded skills are filtered _out_ of the rendered index, and their activation
1104
1222
  lands at the head of the transcript, inside the cached prefix.
1105
- - **`allow`** — how one catalog serves several agents without each seeing all of
1223
+ - **`allow`** - how one catalog serves several agents without each seeing all of
1106
1224
  it. A `preload` entry must also be in `allow`.
1107
1225
 
1108
1226
  ### 5.3 Memory
1109
1227
 
1110
1228
  Memory is the read-write twin of skills: written by the run, not authored. It is
1111
- a **graph** — a node is one thing worth keeping, an edge says how one led to
1112
- another — so recall returns the connected piece rather than a ranked list, and a
1229
+ a **graph** - a node is one thing worth keeping, an edge says how one led to
1230
+ another - so recall returns the connected piece rather than a ranked list, and a
1113
1231
  remembered script arrives with the request that asked for it.
1114
1232
 
1115
1233
  It is configured in `agents.yaml`: a top-level `memory:` block says where the
1116
1234
  graph lives and what vectorises it, and `agents[].memory` says what each agent
1117
- may do with it. It lives with the **project**, not the session — a memory that
1235
+ may do with it. It lives with the **project**, not the session - a memory that
1118
1236
  died with the session would be a cache. Recall is automatic on turns that follow
1119
1237
  new user input, which is what makes memory work without the model remembering to
1120
1238
  look.
@@ -1131,25 +1249,58 @@ Every node carries an **audience**, and an agent's `sees` is the set it reads:
1131
1249
  one graph, masked per agent, never one graph each. The four `memory_*` tools are
1132
1250
  derived from `access` and are never listed in `tools:`.
1133
1251
 
1252
+ **Memory comes on.** `zen init` writes the block above, `memory: true` on the
1253
+ default agent, and the half nobody would guess was part of it:
1254
+ `agents/memory-instructions.md`, a copy of the `zen-memory` skill's
1255
+ `references/memory-instructions.md`, which the project already holds. Adding
1256
+ memory to a project that has none is both of those edits, and a copy that was
1257
+ lost or went stale is one command:
1258
+
1259
+ ```sh
1260
+ .github/skills/zen-memory/scripts/check-instructions.sh fix
1261
+ ```
1262
+
1263
+ It is what tells the agent that the store is reached only through the tools, that
1264
+ `memory_search` clips a node's text and `memory_load` reads it whole, that a
1265
+ remembered file's path comes from `memory_load` and not from listing `/memory`,
1266
+ what a node must carry to be worth having, and that a correction is a
1267
+ `SUPERSEDES` edge rather than an edit. It is its own file rather than a section of
1268
+ `agents/instructions.md` because it arrives with the `memory:` block and leaves
1269
+ with it.
1270
+
1271
+ **Leave that file as a pure copy.** Anything this project decides for itself -
1272
+ which audience holds what, what must never be written down, what an agent is
1273
+ expected to commit at the end of a job - goes in
1274
+ `agents/memory-policy-instructions.md`. Topic files are read in filename order,
1275
+ so the policy lands directly after the rules it qualifies, and the copy stays
1276
+ re-runnable: the reference is rewritten by every `zen init` and `zen open`, and
1277
+ keeping up with it is then the same one-line command rather than a hand-patch.
1278
+
1279
+ `zen check` validates the `memory:` block and says nothing about the rules'
1280
+ absence, so this is checked every time, by
1281
+ `.github/skills/zen-memory/scripts/check-instructions.sh` and never by reading
1282
+ the two files against each other: they drift by trailing whitespace inside a
1283
+ table, and the script is what can see that.
1284
+
1134
1285
  Do not treat memory as a database. It is for things learned that should
1135
- persist. Anything authoritative — a rate, a policy clause, a procedure — belongs
1286
+ persist. Anything authoritative - a rate, a policy clause, a procedure - belongs
1136
1287
  in a skill, where it is versioned and reviewable.
1137
1288
 
1138
- The full treatment — kinds and relations, how ranking and stitching spend their
1289
+ The full treatment - kinds and relations, how ranking and stitching spend their
1139
1290
  budget, remembered files under `/memory`, audience design, and how to put memory
1140
- in front of a `zen rag` index — is in the `zen-memory` skill.
1291
+ in front of a `zen rag` index - is in the `zen-memory` skill.
1141
1292
 
1142
1293
  ### 5.4 Cache discipline
1143
1294
 
1144
1295
  Rules that follow directly from "stable prefix = cache hit":
1145
1296
 
1146
- - Do not reorder `agents.yaml` for cosmetic reasons — tool order is prompt order.
1147
- - Do not put timestamps, run ids, or "today is …" in `INSTRUCTIONS.md` or a
1297
+ - Do not reorder `agents.yaml` for cosmetic reasons - tool order is prompt order.
1298
+ - Do not put timestamps, run ids, or "today is …" in a house-rules file or a
1148
1299
  prompt. A changing prefix is a permanent cache miss. Put volatile facts in a
1149
1300
  tool result.
1150
1301
  - Prefer `preload` over an instruction telling the model to load a skill first.
1151
1302
  - Keep the volatile half of an instruction in the agent prompt and the stable
1152
- half in `INSTRUCTIONS.md`, not the reverse.
1303
+ half in the house rules, not the reverse.
1153
1304
 
1154
1305
  ---
1155
1306
 
@@ -1169,10 +1320,10 @@ Split into a second agent when at least one is true:
1169
1320
 
1170
1321
  Do **not** split because:
1171
1322
 
1172
- - The prompt got long — split it into skills instead.
1173
- - It feels tidier — every handoff costs a full re-render of the system prompt and
1323
+ - The prompt got long - split it into skills instead.
1324
+ - It feels tidier - every handoff costs a full re-render of the system prompt and
1174
1325
  a fresh cache prefix.
1175
- - You want "a planner and an executor" with no distinct tools or models — that is
1326
+ - You want "a planner and an executor" with no distinct tools or models - that is
1176
1327
  one agent with a numbered procedure.
1177
1328
 
1178
1329
  **Start with one agent. Add the second when a specific case forces it.**
@@ -1181,7 +1332,7 @@ Do **not** split because:
1181
1332
 
1182
1333
  **Router + specialists.** A cheap, fast agent whose only job is classification
1183
1334
  and handoff. Its prompt is short, it holds few tools, and it must be forbidden
1184
- from answering. Every specialist hands back to it — a handoff does not return on
1335
+ from answering. Every specialist hands back to it - a handoff does not return on
1185
1336
  its own, so without that edge the first routed question is the last one routed.
1186
1337
 
1187
1338
  ```yaml
@@ -1193,11 +1344,11 @@ agents:
1193
1344
  ```
1194
1345
 
1195
1346
  **Pipeline.** Fixed stages, each handing to the next; only the last answers the
1196
- user. Encode the order in `handoffs:` so a stage cannot skip ahead — and close
1347
+ user. Encode the order in `handoffs:` so a stage cannot skip ahead - and close
1197
1348
  the loop, by handing back from the last stage to the first.
1198
1349
 
1199
- **Fan-out / join.** For independent parallel work — ten regions, four review
1200
- lenses, six candidate suppliers — declare `fork:` on the agent that owns the
1350
+ **Fan-out / join.** For independent parallel work - ten regions, four review
1351
+ lenses, six candidate suppliers - declare `fork:` on the agent that owns the
1201
1352
  work and let the model split it. See §6.4.
1202
1353
 
1203
1354
  **Single agent + rich catalog.** One agent, `discovery: index`, twenty skills.
@@ -1208,11 +1359,11 @@ multi-agent instinct suggests.
1208
1359
 
1209
1360
  - `description:` on the target agent is what the model reads when deciding.
1210
1361
  Write it as a routing condition: _"Applies the written peril policies to a
1211
- claim and explains the outcome."_ — not _"The adjuster agent."_
1362
+ claim and explains the outcome."_ - not _"The adjuster agent."_
1212
1363
  - Handoffs are bare name strings; there is no per-edge configuration.
1213
1364
  - Self-handoff is a load error.
1214
1365
  - **Every handoff must be returnable.** For every `A → B` there must be a path
1215
- from `B` back to `A`. Indirect counts — `B → C → A` is a way home — so the
1366
+ from `B` back to `A`. Indirect counts - `B → C → A` is a way home - so the
1216
1367
  rule is that every handoff edge lies on a cycle. `zen check` warns
1217
1368
  (`handoff.one-way`) on any edge that does not.
1218
1369
  - Handoff collapses history by policy: the receiving agent sees a selection, not
@@ -1221,7 +1372,7 @@ multi-agent instinct suggests.
1221
1372
 
1222
1373
  **Why the return edge is not optional.** A handoff is a one-way door: control
1223
1374
  moves and stays moved. Nothing hands it back, so an agent reached by a dead-end
1224
- edge owns the conversation for the rest of the session — the next question,
1375
+ edge owns the conversation for the rest of the session - the next question,
1225
1376
  whatever it is about, is answered by the specialist the router sent the user to.
1226
1377
  The drawn architecture holds for one turn and then quietly stops being the
1227
1378
  architecture.
@@ -1234,10 +1385,11 @@ interchangeable:
1234
1385
  - The other agent should **own the conversation from here** → `handoffs:`, and
1235
1386
  something on the far side has to lead back.
1236
1387
  - This agent needs **an answer and then carries on** → `fork:`. Never a handoff:
1237
- a handoff spends the conversation to get the answer.
1388
+ a handoff spends the conversation to get the answer. One branch is enough -
1389
+ a fork of one is exactly "ask the specialist and continue".
1238
1390
 
1239
1391
  But the return is **condensed**: `A → fork → B` rejoins as one tool result
1240
- holding B's answer, so A never sees the steps B took — no tool calls, no files
1392
+ holding B's answer, so A never sees the steps B took - no tool calls, no files
1241
1393
  read, no intermediate reasoning. That is what makes fanning out cheap, and it is
1242
1394
  what to design around: whatever A will need must be _in_ the answer, so say so in
1243
1395
  B's prompt. Work whose value is the trace rather than the conclusion does not
@@ -1262,47 +1414,51 @@ agents:
1262
1414
  | Field | Default | Meaning |
1263
1415
  | ------------- | -------------------- | ------------------------------------- |
1264
1416
  | `agents` | every declared agent | Which agents a branch may run |
1265
- | `maxBranches` | unlimited | Cap on branches per call; minimum `2` |
1417
+ | `maxBranches` | unlimited | Cap on branches per call; minimum `1` |
1266
1418
 
1267
1419
  The **model** decides the split: it names N branches, each with self-contained
1268
1420
  instructions. They run truly concurrently and rejoin as **one tool call and one
1269
- tool result** in the parent's history — so N branches cost the parent
1421
+ tool result** in the parent's history - so N branches cost the parent
1270
1422
  O(N × summary), not O(N × full history).
1271
1423
 
1272
1424
  Rules worth knowing before you write the key:
1273
1425
 
1274
- - `agents:` **may include the forking agent itself** — unlike `handoffs:`, that
1426
+ - `agents:` **may include the forking agent itself** - unlike `handoffs:`, that
1275
1427
  is not an error, and one role fanned out over ten items is the common shape.
1276
1428
  The list reaches the model as an `enum`, so a name outside it cannot even be
1277
1429
  decoded.
1278
- - A fork always needs **at least two** branches. A one-branch call is refused
1279
- with a message telling the model to do the work itself instead.
1430
+ - A fork of **one** branch is delegation, and it is valid: one assignment run
1431
+ elsewhere, its conclusion returned, its transcript left out of this
1432
+ conversation. Use it when another agent suits the job better, or when the job
1433
+ would fill the caller's context with material it has no use for afterwards.
1280
1434
  - **Branches cannot talk to each other.** If branch B needs branch A's answer,
1281
- it is a sequence, not a fork — keep it in one conversation.
1435
+ it is a sequence, not a fork - keep it in one conversation.
1282
1436
  - Nesting is capped by the run's `maxForkDepth` (2 by default), so a branch may
1283
1437
  fork again but not without bound.
1284
1438
  - Fork vs handoff: a handoff is _one_ conversation changing owner; a fork is the
1285
1439
  _same_ question asked N times at once and merged. **A fork returns; a handoff
1286
- does not** — which is why a handoff needs a return path (§6.3) and a fork
1440
+ does not** - which is why a handoff needs a return path (§6.3) and a fork
1287
1441
  needs nothing.
1288
1442
 
1289
- Choose `context:` deliberately — the model sets it per call, so say in the
1443
+ Choose `context:` deliberately - the model sets it per call, so say in the
1290
1444
  agent's prompt which one this work wants:
1291
1445
 
1292
- - `inherit` — branch gets the full prefix. For work that depends on the case.
1293
- - `compact` — prefix minus tool calls, tool results and recalls: _what was
1446
+ - `inherit` - branch gets the full prefix. For work that depends on the case.
1447
+ - `compact` - prefix minus tool calls, tool results and recalls: _what was
1294
1448
  decided_, not the raw noise. **The default for wide fan-outs.**
1295
- - `none` — system prompt plus instructions only. Cheapest; for independent
1449
+ - `none` - system prompt plus instructions only. Cheapest; for independent
1296
1450
  lookups.
1297
1451
 
1298
- Declaring `fork:` only makes the tool available. Say in the agent's prompt when
1299
- to reach for it, in the terms of this domain — _"When the request covers more
1300
- than one region, fork one branch per region and merge their findings"_ — or a
1301
- weaker model will work through the list serially and never call it.
1452
+ Declaring `fork:` only makes the tool available. What a fork costs and what
1453
+ survives the join is explained to the model by the runtime, in the system prompt
1454
+ of every agent holding the key. What is left to you is _when_, in the terms of
1455
+ this domain - _"When the request covers more than one region, fork one branch
1456
+ per region and merge their findings"_ - or a weaker model will work through the
1457
+ list serially and never call it.
1302
1458
 
1303
1459
  ### 6.5 Termination
1304
1460
 
1305
- A turn ends when the model replies with no tool calls — that text is the answer.
1461
+ A turn ends when the model replies with no tool calls - that text is the answer.
1306
1462
  There is no turn limit, so an agent that must stop somewhere needs a prompt that
1307
1463
  says where: what "done" looks like, and what to do when it cannot get there.
1308
1464
 
@@ -1315,7 +1471,7 @@ should contain. An agent with no stated finish keeps working the problem.
1315
1471
 
1316
1472
  ### 7.1 Tiers
1317
1473
 
1318
- Think in three tiers, and give them **role names, not vendor names** — so the
1474
+ Think in three tiers, and give them **role names, not vendor names** - so the
1319
1475
  mapping changes in one place.
1320
1476
 
1321
1477
  | Alias | Role | Reasoning |
@@ -1379,7 +1535,7 @@ Only the **first** colon separates, so a fine-tuned id must name its provider:
1379
1535
  `openai:ft:gpt-4o:acme::a1b2`.
1380
1536
 
1381
1537
  **Always write the prefix.** The first segment is a provider _name_, not a vendor
1382
- hint — nothing reads `gemini-3.5-flash` and infers Google. An unprefixed id goes
1538
+ hint - nothing reads `gemini-3.5-flash` and infers Google. An unprefixed id goes
1383
1539
  to the default provider, which is `openai` unless a top-level `provider:` says
1384
1540
  otherwise, so a bare `gemini-3.5-flash` asks OpenAI for a Google model and fails
1385
1541
  with `OPENAI_API_KEY is not set`. The message names the provider it resolved to;
@@ -1398,26 +1554,26 @@ over one connection.
1398
1554
 
1399
1555
  | Field | Applies to | Notes |
1400
1556
  | ----------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------- |
1401
- | `reasoningEffort` | openai, openrouter | Free string on purpose — the API is the authority on validity. On openai **needs `api: responses`** — §7.6 |
1402
- | `reasoningSummary` | openai, openrouter | `auto` \| `concise` \| `detailed`. On openai **needs `api: responses`** — §7.6 |
1557
+ | `reasoningEffort` | openai, openrouter | Free string on purpose - the API is the authority on validity. On openai **needs `api: responses`** - §7.6 |
1558
+ | `reasoningSummary` | openai, openrouter | `auto` \| `concise` \| `detailed`. On openai **needs `api: responses`** - §7.6 |
1403
1559
  | `maxTokens` | anthropic, gemini, openrouter | Cap on **output** tokens, not context. Anthropic requires one (default 8192) |
1404
1560
  | `thinkingBudgetTokens` | anthropic | Extended thinking budget |
1405
1561
  | `thinkingBudget` | gemini 2.5 | Tokens: `0` off, `-1` auto |
1406
1562
  | `thinkingLevel` | gemini 3 | `minimal` \| `low` \| `medium` \| `high` |
1407
1563
  | `includeThoughts` | gemini | Thought summaries; default `true` |
1408
- | `routing` / `fallbacks` | openrouter | Upstream provider preferences, and models to fall back to — §7.5 |
1564
+ | `routing` / `fallbacks` | openrouter | Upstream provider preferences, and models to fall back to - §7.5 |
1409
1565
 
1410
1566
  Knobs that do not apply to the chosen vendor are ignored, not rejected.
1411
- `api:` exists only for the OpenAI protocol — naming it on a Gemini or Anthropic
1567
+ `api:` exists only for the OpenAI protocol - naming it on a Gemini or Anthropic
1412
1568
  model is an error.
1413
1569
 
1414
1570
  Each vendor's own SDK is used rather than its OpenAI-compatible endpoint, because
1415
1571
  those endpoints drop exactly what this runtime is built on: thinking budgets,
1416
- thought signatures, cache accounting. `openai-compatible` is the shim kind — vLLM,
1572
+ thought signatures, cache accounting. `openai-compatible` is the shim kind - vLLM,
1417
1573
  a self-hosted gateway. `openrouter` used to be that shim with its base url
1418
1574
  (`https://openrouter.ai/api/v1`) and key env (`OPENROUTER_API_KEY`) filled in; it
1419
1575
  now has its own SDK, which is what makes provider routing and fallback chains
1420
- available — §7.5.
1576
+ available - §7.5.
1421
1577
 
1422
1578
  ### 7.5 OpenRouter
1423
1579
 
@@ -1425,7 +1581,7 @@ A gateway: one key and one endpoint in front of several hundred models from
1425
1581
  every vendor. Useful when a project wants to compare families without holding
1426
1582
  four accounts, and when a cheap tier should be swappable by editing one id.
1427
1583
 
1428
- The whole declaration is the kind — `baseURL` and `OPENROUTER_API_KEY` are its
1584
+ The whole declaration is the kind - `baseURL` and `OPENROUTER_API_KEY` are its
1429
1585
  defaults, and built-in kinds are usable as provider names, so a `providers:`
1430
1586
  entry is only worth writing when it adds something:
1431
1587
 
@@ -1435,7 +1591,7 @@ agents:
1435
1591
  model: openrouter:anthropic/claude-sonnet-4.5
1436
1592
  ```
1437
1593
 
1438
- **Model ids** are `vendor/model` and may carry a variant suffix after a colon —
1594
+ **Model ids** are `vendor/model` and may carry a variant suffix after a colon -
1439
1595
  `:free`, `:nitro` (throughput-routed), `:floor` (price-routed), `:online` (web
1440
1596
  search). Both survive the shorthand, because only the _first_ colon separates:
1441
1597
 
@@ -1458,7 +1614,7 @@ the destination model understands.
1458
1614
  the upstream provider (OpenRouter's `provider` field, renamed because `provider:`
1459
1615
  already means the connection); `fallbacks` lists other _models_ to try when none
1460
1616
  can serve it (its `models` field). `allowFallbacks`, below, is a third thing
1461
- again — whether the gateway may look past `order`:
1617
+ again - whether the gateway may look past `order`:
1462
1618
 
1463
1619
  ```yaml
1464
1620
  models:
@@ -1478,9 +1634,9 @@ there is a load error.
1478
1634
 
1479
1635
  | Key | Value |
1480
1636
  | ------------------- | ---------------------------------------------------------------------------- |
1481
- | `order` | Providers to try first — a _preference_, not a restriction |
1637
+ | `order` | Providers to try first - a _preference_, not a restriction |
1482
1638
  | `only` / `ignore` | Restrict serving to, or away from, these |
1483
- | `allowFallbacks` | May the gateway go beyond `order` — **on** unless set `false` |
1639
+ | `allowFallbacks` | May the gateway go beyond `order` - **on** unless set `false` |
1484
1640
  | `sort` | `price`, `throughput`, `latency`, `exacto` |
1485
1641
  | `requireParameters` | Skip a provider that would drop a parameter rather than serve it |
1486
1642
  | `dataCollection` | `allow` \| `deny` |
@@ -1491,12 +1647,12 @@ there is a load error.
1491
1647
  sits alongside `routing`, not inside it.
1492
1648
 
1493
1649
  **A typo in `order` is invisible.** Provider names and `sort` are free strings
1494
- for the same reason `reasoningEffort` is — the gateway's list moves faster than a
1495
- schema would — so nothing local rejects them, and since `allowFallbacks` defaults
1650
+ for the same reason `reasoningEffort` is - the gateway's list moves faster than a
1651
+ schema would - so nothing local rejects them, and since `allowFallbacks` defaults
1496
1652
  on, an unknown name is skipped and the request quietly succeeds somewhere else.
1497
1653
  What _is_ checked is checked by the API rather than at load: a bad `sort` or
1498
1654
  `quantizations` returns `400 provider.sort: Invalid input`. Use `only`, or
1499
- `allowFallbacks: false`, when the constraint is meant to bind — an unroutable
1655
+ `allowFallbacks: false`, when the constraint is meant to bind - an unroutable
1500
1656
  request is then a 404 instead of a silent reroute.
1501
1657
 
1502
1658
  **Attribution** goes in `headers:`; there is no dedicated field because that one
@@ -1527,11 +1683,11 @@ curl -s https://openrouter.ai/api/v1/models | jq -r '
1527
1683
  `supported_parameters` decides whether `tools`, `tool_choice` and
1528
1684
  `reasoning_effort` are honoured. An agent with tools needs `tools` in that list.
1529
1685
 
1530
- **Not modelled yet:** `transforms`, `usage.include`, and per-call cost — the
1686
+ **Not modelled yet:** `transforms`, `usage.include`, and per-call cost - the
1531
1687
  gateway reports a price on every response, but it is not surfaced in the token
1532
1688
  accounting. `plugins` exists in code only. Four `provider` fields the SDK accepts
1533
- have no yaml spelling either — `maxPrice`, `preferredMaxLatency`,
1534
- `preferredMinThroughput`, `enforceDistillableText` — and `sort` takes the string
1689
+ have no yaml spelling either - `maxPrice`, `preferredMaxLatency`,
1690
+ `preferredMinThroughput`, `enforceDistillableText` - and `sort` takes the string
1535
1691
  form only, not the `{ by, partition }` object. `models:` entries are strict, so
1536
1692
  writing any of these is a load error rather than a key that is silently dropped.
1537
1693
 
@@ -1545,27 +1701,27 @@ in place. `timeoutMs` and `headers` behave normally.
1545
1701
 
1546
1702
  Ask two separate questions: does the model **reason**, and does it **say what it
1547
1703
  reasoned**. They are different knobs, and the second is off by default on every
1548
- vendor except Gemini — which is why a reasoning model can burn thousands of
1704
+ vendor except Gemini - which is why a reasoning model can burn thousands of
1549
1705
  thinking tokens while the CLI shows no progress at all.
1550
1706
 
1551
- **OpenAI** — reasoning text only exists on the **responses** API, and only as a
1707
+ **OpenAI** - reasoning text only exists on the **responses** API, and only as a
1552
1708
  summary. `api: responses` is not optional here, and not only for visibility:
1553
1709
  chat completions is the default, and it **refuses `reasoningEffort` together with
1554
- function tools** — `400 Function tools with reasoning_effort are not supported
1710
+ function tools** - `400 Function tools with reasoning_effort are not supported
1555
1711
  for <model> in /v1/chat/completions`. So any OpenAI agent that both reasons and
1556
- holds tools — which is nearly all of them — must name the api.
1712
+ holds tools - which is nearly all of them - must name the api.
1557
1713
 
1558
1714
  ```yaml
1559
1715
  models:
1560
1716
  default:
1561
1717
  provider: openai
1562
- api: responses # required — chat completions refuses tools + reasoning
1718
+ api: responses # required - chat completions refuses tools + reasoning
1563
1719
  model: gpt-5.4-nano
1564
1720
  reasoningEffort: medium # how hard it thinks
1565
1721
  reasoningSummary: auto # whether you get to see it
1566
1722
  ```
1567
1723
 
1568
- **Anthropic** — `thinkingBudgetTokens` turns extended thinking on, and it is spent
1724
+ **Anthropic** - `thinkingBudgetTokens` turns extended thinking on, and it is spent
1569
1725
  _out of_ `maxTokens`, so raise that too or the answer has no room left after the
1570
1726
  thinking. The runtime keeps 1024 tokens of headroom whatever you write, so a cap
1571
1727
  below the budget is corrected rather than rejected. Read §7.7 first: this
@@ -1580,7 +1736,7 @@ models:
1580
1736
  thinkingBudgetTokens: 8000
1581
1737
  ```
1582
1738
 
1583
- **Gemini** — thought summaries are on by default (`includeThoughts: true`); what
1739
+ **Gemini** - thought summaries are on by default (`includeThoughts: true`); what
1584
1740
  varies is the budget. Gemini 3 takes `thinkingLevel`, Gemini 2.5 takes
1585
1741
  `thinkingBudget` in tokens.
1586
1742
 
@@ -1597,19 +1753,19 @@ running tail above the answer, and the one-shot path prints it under `--live`.
1597
1753
  The full chain is kept in the trajectory either way and is in the inspect report,
1598
1754
  so turning summaries off costs visibility, not the audit trail.
1599
1755
 
1600
- The cost is real — a summary is extra output tokens on every call — so leave it
1756
+ The cost is real - a summary is extra output tokens on every call - so leave it
1601
1757
  on where someone is watching and reach for a cheaper tier before turning effort
1602
1758
  up (§7.8).
1603
1759
 
1604
1760
  ### 7.7 Known traps
1605
1761
 
1606
- - **A model id with no provider prefix** — `gemini-3.5-flash` resolves to the
1762
+ - **A model id with no provider prefix** - `gemini-3.5-flash` resolves to the
1607
1763
  default provider, not to Google, and the failure reads as a missing OpenAI key.
1608
- Write `google:gemini-3.5-flash` — §7.3.
1609
- - **OpenAI reasoning without `api: responses`** — chat completions rejects
1764
+ Write `google:gemini-3.5-flash` - §7.3.
1765
+ - **OpenAI reasoning without `api: responses`** - chat completions rejects
1610
1766
  `reasoningEffort` alongside function tools with a `400`, and exposes no
1611
- reasoning summary even without tools — §7.6.
1612
- - **Anthropic + `thinkingBudgetTokens` + multi-turn tool use** — thinking-block
1767
+ reasoning summary even without tools - §7.6.
1768
+ - **Anthropic + `thinkingBudgetTokens` + multi-turn tool use** - thinking-block
1613
1769
  signatures are not replayed, and the API rejects the follow-up. Leave extended
1614
1770
  thinking off for tool-using agents.
1615
1771
  - **`reasoningEffort: minimal`** is rejected by the `gpt-5.4-*` family; use `low`.
@@ -1617,16 +1773,16 @@ up (§7.8).
1617
1773
  → the `project_id` inside the key file named by `GOOGLE_APPLICATION_CREDENTIALS`.
1618
1774
  gcloud user credentials and metadata-server credentials carry no project id, so
1619
1775
  those deployments must set the variable.
1620
- - **OpenRouter + a capability the route does not have** — a valid id and a valid
1776
+ - **OpenRouter + a capability the route does not have** - a valid id and a valid
1621
1777
  key still fail at the first request (`404 No endpoints found that support image
1622
1778
  input`, or tools quietly unused). This is not a config error and `zen check`
1623
1779
  cannot see it: check the catalog (§7.5). Cheap `:free` tiers are the usual
1624
- offenders — they are frequently text-only.
1780
+ offenders - they are frequently text-only.
1625
1781
  - **Swapping an OpenRouter id is not a like-for-like change.** Two models behind
1626
1782
  one gateway differ in modalities, tool support and reasoning; re-run the case
1627
1783
  that uses the capability, not just any case.
1628
1784
  - **An `embeddings:` entry on an Anthropic provider** fails at load: that vendor
1629
- publishes no embeddings API. Point it at another provider — §3.1.1.
1785
+ publishes no embeddings API. Point it at another provider - §3.1.1.
1630
1786
  - **Changing `dimensions:` on an embedding invalidates every stored vector.**
1631
1787
  Widths are not comparable, so anything already indexed has to be re-embedded.
1632
1788
 
@@ -1634,7 +1790,7 @@ input`, or tools quietly unused). This is not a config error and `zen check`
1634
1790
 
1635
1791
  1. Start every agent on `balanced`.
1636
1792
  2. Demote to `router` any agent whose job is classification, extraction, or a
1637
- fixed handoff — measure, do not guess.
1793
+ fixed handoff - measure, do not guess.
1638
1794
  3. Promote to `careful` only after seeing a concrete failure that a stronger
1639
1795
  model actually fixes.
1640
1796
  4. Never fix an instruction-following bug with a model upgrade (§4.4).
@@ -1650,18 +1806,18 @@ There is no compiler for prose. Substitutes, in order of value:
1650
1806
  1. **`zen check`.** Most structural mistakes are load errors, and this is the
1651
1807
  fastest way to see all of them: it validates `agents.yaml`, checks that every
1652
1808
  prompt, skill and catalog it names is on disk, that hand-offs and tool
1653
- selectors resolve, and that the models have credentials — without stopping at
1809
+ selectors resolve, and that the models have credentials - without stopping at
1654
1810
  the first problem and without calling anything. `zen check --json` if you are
1655
1811
  parsing it.
1656
1812
  2. **Run the case that motivated the change**, plus one that must _not_ change.
1657
- 3. **Read the inspect report** — it shows the assembled prompt, every request and
1813
+ 3. **Read the inspect report** - it shows the assembled prompt, every request and
1658
1814
  response, tool calls, skill activations and cost. Behaviour questions are
1659
1815
  answered there, not by re-reading the YAML.
1660
1816
  4. **Re-run `zen check` after any rename.** It is what catches the handoff, the
1661
1817
  skill or the prompt path that a rename silently unlinked.
1662
1818
  5. **Watch the token accounting.** A change that doubles prefix size is a
1663
1819
  regression even if the answer improved.
1664
- 6. **After changing a model id, exercise the capability it was chosen for** —
1820
+ 6. **After changing a model id, exercise the capability it was chosen for** -
1665
1821
  send an image, force a tool call, ask for reasoning. `zen check` proves the
1666
1822
  credential resolves, not that the route serves images or honours `tools`; on a
1667
1823
  gateway that gap is a request-time 404 (§7.5).
@@ -1675,13 +1831,13 @@ first thing that looks like a credential problem:
1675
1831
  zen models test <ref> # one real call, one verdict
1676
1832
  ```
1677
1833
 
1678
- - **refused** — the credential was rejected. `zen key check <provider>`.
1679
- - **blocked** — the credential was _accepted_ and the account said no: an API
1834
+ - **refused** - the credential was rejected. `zen key check <provider>`.
1835
+ - **blocked** - the credential was _accepted_ and the account said no: an API
1680
1836
  switched off, an empty balance, a model this key was never granted. **Do not
1681
- rotate the key** — a new one is refused identically. The fix is printed under
1837
+ rotate the key** - a new one is refused identically. The fix is printed under
1682
1838
  the verdict; for a disabled Google API it is the exact `gcloud services
1683
1839
  enable …` line.
1684
- - **no answer** — the network. Try again.
1840
+ - **no answer** - the network. Try again.
1685
1841
 
1686
1842
  Either run the printed fix, or take a model that works and put the ref it prints
1687
1843
  into `agents.yaml`:
@@ -1701,6 +1857,16 @@ ok, `1` failed, `2` usage, `3` invalid project, `4` no usable credential.
1701
1857
 
1702
1858
  ## 9. Review checklist
1703
1859
 
1860
+ Four of these are decidable, and are one command - `zen check`, the path sweep,
1861
+ the memory copy, the spec-sync record, in that order:
1862
+
1863
+ ```sh
1864
+ .github/skills/zen-review/scripts/review.sh
1865
+ ```
1866
+
1867
+ Run it first and read what it says. Everything else below is judgement, which is
1868
+ why it is still a list. The `zen-review` skill has the detail.
1869
+
1704
1870
  Before finishing any change here:
1705
1871
 
1706
1872
  **Structure**
@@ -1715,26 +1881,34 @@ Before finishing any change here:
1715
1881
 
1716
1882
  **Prompts**
1717
1883
 
1718
- - [ ] `INSTRUCTIONS.md` carries the shared architecture and nothing agent-specific
1719
- - [ ] Nothing duplicated between `INSTRUCTIONS.md` and an agent prompt
1884
+ - [ ] `agents/instructions.md` carries the shared architecture and nothing
1885
+ agent-specific
1886
+ - [ ] Nothing duplicated between a house-rules file and an agent prompt
1887
+ - [ ] Each `agents/<topic>-instructions.md` is about one capability, and is true
1888
+ of every agent
1720
1889
  - [ ] Every tool and agent referenced by its exact name
1721
1890
  - [ ] Failure paths stated for every instruction that can fail
1722
1891
  - [ ] No facts, rates or figures embedded in a prompt
1723
1892
  - [ ] No hedging, no meta-talk about the runtime
1893
+ - [ ] If any agent has `memory:`,
1894
+ `.github/skills/zen-memory/scripts/check-instructions.sh` exits zero -
1895
+ `agents/memory-instructions.md` is the reference byte for byte - and
1896
+ anything project-specific is in `agents/memory-policy-instructions.md` - §5.3
1724
1897
 
1725
- **Paths (§2.5 — run its checklist over the three trees)**
1898
+ **Paths (§2.5 - `.github/skills/zen-review/scripts/check-paths.sh` finds the
1899
+ candidates; these are the judgements to make about each one)**
1726
1900
 
1727
- - [ ] Every absolute path in `INSTRUCTIONS.md`, `agents/prompts/*.md` and
1901
+ - [ ] Every absolute path in `agents/*instructions.md`, `agents/prompts/*.md` and
1728
1902
  `agents/skills/*/SKILL.md` is under `/workspace`, `/assets`, `/skills` or
1729
1903
  `/memory`
1730
- - [ ] No prompt or skill names a project file — `agents.yaml`, a prompt path, a
1904
+ - [ ] No prompt or skill names a project file - `agents.yaml`, a prompt path, a
1731
1905
  skill's repository path, `sandbox/Dockerfile`, `.env`, `sessions/`
1732
1906
  - [ ] No `../`, `~`, or host path; nothing assumes the workspace is the project
1733
1907
  - [ ] Every `/assets/…` and `/skills/<name>/…` path names a committed file
1734
1908
 
1735
1909
  **Skills**
1736
1910
 
1737
- - [ ] Every skill is `<name>/SKILL.md` — no bare `<name>.md` in the catalog
1911
+ - [ ] Every skill is `<name>/SKILL.md` - no bare `<name>.md` in the catalog
1738
1912
  - [ ] Every skill has a `description` that says _when it is needed_
1739
1913
  - [ ] `preload` is reserved for content the model would never decline
1740
1914
  - [ ] `preload` entries also appear in `allow` where `allow` is used
@@ -1746,11 +1920,11 @@ Before finishing any change here:
1746
1920
 
1747
1921
  - [ ] Every agent holds the narrowest set its job needs
1748
1922
  - [ ] An agent that only reads is not holding `write_file`, `apply_patch`,
1749
- `move_file` or `delete_file` — subtract them from `workspace:*`
1923
+ `move_file` or `delete_file` - subtract them from `workspace:*`
1750
1924
  - [ ] `sandbox:*` is granted only where a shell is actually needed
1751
1925
  - [ ] `sandbox.persist: true`, unless a throwaway rootfs is wanted on purpose
1752
1926
  - [ ] The `sandbox:` image carries what the work needs, rather than the prompt
1753
- installing it every run — add it to `sandbox/Dockerfile` if `build:` is used
1927
+ installing it every run - add it to `sandbox/Dockerfile` if `build:` is used
1754
1928
  - [ ] `sandbox.env` lists names only, and nothing credential-shaped
1755
1929
  - [ ] `exa:*` is granted only where the live web is actually needed, and the
1756
1930
  prompt says when to trust it over what the model already believes
@@ -1769,44 +1943,61 @@ Before finishing any change here:
1769
1943
 
1770
1944
  **Secrets**
1771
1945
 
1772
- - [ ] No key literal in YAML, prompt, skill or log — `${VAR}` only
1946
+ - [ ] No key literal in YAML, prompt, skill or log - `${VAR}` only
1773
1947
  - [ ] `.env` is git-ignored
1774
1948
 
1949
+ **Specification** (only where the project has a `SPECIFICATION.md`)
1950
+
1951
+ - [ ] `.github/skills/zen-spec-sync/scripts/snapshot.sh status` does not say
1952
+ `tampered` - the baseline matches its own `manifest.txt`
1953
+ - [ ] `.spec-sync/baseline/applied.txt` names the newest file in
1954
+ `.spec-sync/history/`; a baseline ahead of the history is a pass that
1955
+ claimed work it did not finish
1956
+ - [ ] The newest history entry's `Still open` is what the next pass will trust,
1957
+ and says so honestly - an unchanged specification line is not evidence
1958
+ that anything implements it
1959
+ - [ ] No question in `SPECIFICATION-FEEDBACK.md` has a box marked `[x]`, or
1960
+ exactly one answer left under it, without a matching `✅ Answered` entry -
1961
+ that is an answer given and never folded into the specification
1962
+ - [ ] `.spec-sync/` is committed and `.tmp/` is not
1963
+
1775
1964
  ---
1776
1965
 
1777
1966
  ## 10. Where to change what
1778
1967
 
1779
1968
  | Symptom | Change this |
1780
1969
  | --------------------------------------------- | ------------------------------------------------------------------- |
1781
- | Wrong tone, wrong format, wrong length | `INSTRUCTIONS.md` (all agents) or the agent prompt |
1782
- | Says something forbidden | `INSTRUCTIONS.md` prohibition, stated specifically |
1970
+ | Wrong tone, wrong format, wrong length | The house rules (all agents) or the agent prompt |
1971
+ | Says something forbidden | A house-rules prohibition, stated specifically |
1783
1972
  | Ignores a rule that only applies sometimes | Move the rule into a skill with a sharp description |
1784
1973
  | Never loads the skill it should | The skill's `description`; or `preload` it |
1785
1974
  | Loads too much, answers slowly | `allow:`, `maxIndexEntries:`, or `discovery: search` |
1786
- | Invents a number | A skill holding the figure, or a script that computes it — §3.4.1 |
1787
- | A skill in the catalog is never offered | It is a bare `<name>.md`; move it to `<name>/SKILL.md` — §3.4 |
1788
- | Cannot find a file its own skill names | Absolute `/skills/<name>/…` path, and `sandbox:*` — §3.4.1 |
1789
- | Tries to open `agents.yaml` or its own prompt | The prompt names a file no agent can see — §2.5 |
1790
- | Says a file is missing that is in the project | The workspace is not the project directory — §2.5 |
1791
- | Rewrites a whole file to change one line | A prompt line preferring `apply_patch` — §3.6 |
1975
+ | Invents a number | A skill holding the figure, or a script that computes it - §3.4.1 |
1976
+ | A skill in the catalog is never offered | It is a bare `<name>.md`; move it to `<name>/SKILL.md` - §3.4 |
1977
+ | Cannot find a file its own skill names | Absolute `/skills/<name>/…` path, and `sandbox:*` - §3.4.1 |
1978
+ | Tries to open `agents.yaml` or its own prompt | The prompt names a file no agent can see - §2.5 |
1979
+ | Says a file is missing that is in the project | The workspace is not the project directory - §2.5 |
1980
+ | Rewrites a whole file to change one line | A prompt line preferring `apply_patch` - §3.6 |
1792
1981
  | Edits files it should only be reading | Subtract the mutating tools, or `zen run --read-only` |
1793
1982
  | Cannot run the build or the tests | Grant `sandbox:*`; add the toolchain to `sandbox/Dockerfile` |
1794
- | Installs the same packages on every run | Put them in `sandbox/Dockerfile`, or `sandbox.persist: true` — §3.7 |
1795
- | Answers from stale knowledge of the world | Grant `web_search` + `web_read`, and say when — §3.8 |
1796
- | Cites a page it only saw the excerpt of | A prompt line: `web_read` before quoting — §3.8 |
1797
- | Every web call refuses | No Exa key: `zen key add exa` — `zen check` warns — §3.8 |
1798
- | A model or embedder refuses every call | `zen models test <ref>` — if `blocked`, `zen models pick` — §8 |
1983
+ | Installs the same packages on every run | Put them in `sandbox/Dockerfile`, or `sandbox.persist: true` - §3.7 |
1984
+ | Answers from stale knowledge of the world | Grant `web_search` + `web_read`, and say when - §3.8 |
1985
+ | Cites a page it only saw the excerpt of | A prompt line: `web_read` before quoting - §3.8 |
1986
+ | Every web call refuses | No Exa key: `zen key add exa` - `zen check` warns - §3.8 |
1987
+ | A model or embedder refuses every call | `zen models test <ref>` - if `blocked`, `zen models pick` - §8 |
1799
1988
  | Answers instead of routing | Router prompt prohibition; check `handoffs:` |
1800
1989
  | Routes to the wrong specialist | The target agents' `description:` fields |
1801
- | Gets stuck in the agent it routed to | The target needs a handoff back — §6.3 |
1990
+ | Gets stuck in the agent it routed to | The target needs a handoff back - §6.3 |
1802
1991
  | Loses a detail after a handoff | Say it in the handoff; check the collapse policy |
1803
- | Works through N independent items serially | `fork:` on that agent, and a prompt line — §6.4 |
1992
+ | Works through N independent items serially | `fork:` on that agent, and a prompt line - §6.4 |
1804
1993
  | Forks when the steps actually depend | Prompt line: branches cannot see each other |
1805
1994
  | Slow and expensive on trivial cases | Demote that agent's model tier / reasoning effort |
1806
1995
  | Fails only on genuinely hard cases | Promote that agent's tier, or split the hard path out |
1807
- | Shows no reasoning while it works | Turn summaries on for that model — §7.6 |
1996
+ | Shows no reasoning while it works | Turn summaries on for that model - §7.6 |
1808
1997
  | Forgets across conversations | Continue the session rather than starting a new one |
1809
- | Breaks at load with a named path | Read the message — it names the exact key |
1998
+ | Greps `/memory` or reads `graph.json` | `agents/memory-instructions.md` is missing the block - §5.3 |
1999
+ | Recalls a note it cannot check or continue | Same block: it is what requires provenance on every node - §5.3 |
2000
+ | Breaks at load with a named path | Read the message - it names the exact key |
1810
2001
 
1811
2002
  ---
1812
2003
 
@@ -1815,32 +2006,39 @@ Before finishing any change here:
1815
2006
  - **The mega-prompt.** 300 lines covering twelve scenarios. Split into skills.
1816
2007
  - **Agent sprawl.** Eight agents that share one model, one tool set and one
1817
2008
  prompt style. Collapse into one with a catalog.
1818
- - **Facts in prompts.** A fee schedule inside `INSTRUCTIONS.md`. It cannot be
1819
- versioned, cannot be shared, and is paid for on every call.
2009
+ - **Facts in prompts.** A fee schedule inside `agents/instructions.md`. It cannot
2010
+ be versioned, cannot be shared, and is paid for on every call.
1820
2011
  - **The bare skill file.** `agents/skills/<name>.md` instead of a folder. It can
1821
- never grow the table or script the next revision of the rule will want — §3.4.
2012
+ never grow the table or script the next revision of the rule will want - §3.4.
1822
2013
  - **Pointing an agent at the project.** A prompt or skill naming `agents.yaml`,
1823
- `INSTRUCTIONS.md`, `agents/skills/<name>/…` or `sandbox/Dockerfile`. None of
1824
- them are on any path the agent can open, so the instruction fails on the turn
1825
- that follows it — §2.5.
2014
+ `agents/instructions.md`, `agents/skills/<name>/…` or `sandbox/Dockerfile`.
2015
+ None of them are on any path the agent can open, so the instruction fails on
2016
+ the turn that follows it - §2.5.
1826
2017
  - **Arithmetic in prose.** A skill that walks the model through a calculation it
1827
- will get wrong, in a folder that could have held the script — §3.4.1.
2018
+ will get wrong, in a folder that could have held the script - §3.4.1.
2019
+ - **The unguarded store.** `memory:` turned on and the skill's
2020
+ `references/memory-instructions.md` never copied to
2021
+ `agents/memory-instructions.md`, so nothing tells the agent the store is
2022
+ reached only through the tools - and `zen check` passes throughout - §5.3.
2023
+ - **Reviewing a capability without its skill.** A project whose memory, index or
2024
+ sandbox was configured by an earlier pass looks finished because it loads. The
2025
+ obligations are in the editor skill, not in `agents.yaml` - §0.1.
1828
2026
  - **Politeness padding.** "Please try your best to be helpful." Costs tokens,
1829
2027
  changes nothing.
1830
2028
  - **Commented implementation notes.** `agents.yaml` explaining how skill
1831
- discovery or prompt caching works. That is this file's job — §3.1.
2029
+ discovery or prompt caching works. That is this file's job - §3.1.
1832
2030
  - **Fixing prompts with models.** See §4.4.
1833
2031
  - **The chatty router.** A router that answers before handing off, because its
1834
2032
  prompt never forbade it.
1835
2033
  - **The one-way handoff.** A specialist with no way back, so the router routes
1836
- once and then never sees the conversation again — §6.3.
2034
+ once and then never sees the conversation again - §6.3.
1837
2035
  - **Handing off for an answer.** Wanting one lookup and spending the whole
1838
- conversation on it. That is a `fork:`, which returns by itself — §6.4.
2036
+ conversation on it. That is a `fork:`, which returns by itself - §6.4.
1839
2037
  - **Forking a chain.** Branches never see each other, so a fork whose second
1840
2038
  branch needs the first branch's answer is a sequence wearing a fork's clothes.
1841
2039
  - **Installing the toolchain every run.** A prompt that begins with `apt-get
1842
- install` is an `image:` that was never set — §3.7.
1843
- - **Volatile prefix.** "Current date: …" in `INSTRUCTIONS.md`. Permanent cache
2040
+ install` is an `image:` that was never set - §3.7.
2041
+ - **Volatile prefix.** "Current date: …" in the house rules. Permanent cache
1844
2042
  miss.
1845
2043
 
1846
2044
  ---
@@ -1849,16 +2047,18 @@ install` is an `image:` that was never set — §3.7.
1849
2047
 
1850
2048
  This file is loaded on every request in this repository, so it is subject to its
1851
2049
  own rules: stable, factual, no hedging. Before adding a section, ask whether it
1852
- is true of every task — if it is not, it is a skill under `.github/skills/`, or
2050
+ is true of every task - if it is not, it is a skill under `.github/skills/`, or
1853
2051
  a prompt file under `.github/prompts/` if it is a procedure (§2.4).
1854
2052
 
1855
2053
  Update it when:
1856
2054
 
1857
2055
  - the layout changes (a new directory, a moved catalog)
1858
- - a model alias is added, retired or repointed — §7.2 must match `agents.yaml`
1859
- - a vendor trap is discovered — add it to §7.7
1860
- - a recurring review comment appears twice — turn it into a checklist line in §9
1861
- - a runtime capability is added — describe it here, or it will not be used
2056
+ - a model alias is added, retired or repointed - §7.2 must match `agents.yaml`
2057
+ - a vendor trap is discovered - add it to §7.7
2058
+ - a recurring review comment appears twice - turn it into a checklist line in §9
2059
+ - a runtime capability is added - describe it here, or it will not be used
2060
+ - an editor skill starts requiring a block in the target project - name the
2061
+ requirement in §0.1 and the check for it in §9, or it will be missed
1862
2062
 
1863
2063
  Do not let it grow without pruning. When a section is only true of one agent, it
1864
2064
  belongs in that agent's prompt, not here.