@zenera/cli 1.1.14 → 1.1.16

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 (32) hide show
  1. package/README.md +563 -206
  2. package/dist/banner.d.ts +5 -1
  3. package/dist/banner.js +2 -2
  4. package/dist/command.d.ts +5 -0
  5. package/dist/commands/init.js +46 -2
  6. package/dist/main.js +10 -6
  7. package/dist/validate.js +1 -1
  8. package/package.json +2 -2
  9. package/templates/editor/.github/copilot-instructions.md +253 -218
  10. package/templates/editor/.github/prompts/new-agent.prompt.md +6 -6
  11. package/templates/editor/.github/prompts/new-skill.prompt.md +7 -7
  12. package/templates/editor/.github/prompts/review-project.prompt.md +1 -1
  13. package/templates/editor/.github/prompts/sync-with-spec.prompt.md +26 -26
  14. package/templates/editor/.github/skills/zen-cli/SKILL.md +62 -6
  15. package/templates/editor/.github/skills/zen-cli/references/check.md +3 -3
  16. package/templates/editor/.github/skills/zen-cli/references/faker.md +14 -14
  17. package/templates/editor/.github/skills/zen-cli/references/frame.md +6 -6
  18. package/templates/editor/.github/skills/zen-cli/references/inspect.md +3 -3
  19. package/templates/editor/.github/skills/zen-cli/references/keys.md +9 -9
  20. package/templates/editor/.github/skills/zen-cli/references/memory.md +11 -11
  21. package/templates/editor/.github/skills/zen-cli/references/models.md +8 -8
  22. package/templates/editor/.github/skills/zen-cli/references/projects.md +3 -3
  23. package/templates/editor/.github/skills/zen-cli/references/rag.md +191 -56
  24. package/templates/editor/.github/skills/zen-cli/references/run.md +3 -3
  25. package/templates/editor/.github/skills/zen-cli/references/sandbox.md +8 -8
  26. package/templates/editor/.github/skills/zen-memory/SKILL.md +50 -50
  27. package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +71 -48
  28. package/templates/editor/.github/skills/zen-rag-schema/SKILL.md +67 -67
  29. package/templates/editor/.vscode/settings.json +5 -2
  30. package/templates/project/INSTRUCTIONS.md +1 -1
  31. package/templates/project/assets/README.md +1 -1
  32. package/templates/project/sandbox/Dockerfile.tmpl +3 -0
@@ -1,22 +1,22 @@
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
9
+ > rewrites the whole tree on `init` and `open` - so put this project's own
10
10
  > conventions in `INSTRUCTIONS.md`, where they will survive. The runtime's own
11
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.
12
+ > @zenera/neo repository - where they disagree with this file, they win.
13
13
 
14
14
  ---
15
15
 
16
16
  ## 0. What this repository is
17
17
 
18
18
  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
19
+ here. It is a folder of declarative artefacts - YAML and Markdown - that
20
20
  assemble into a running multi-agent system, driven by the `zen` CLI.
21
21
 
22
22
  The centre of gravity is **prose**. Every valuable change here is an edit to a
@@ -34,6 +34,40 @@ programmed: what an agent knows, which model answers, and which of the tools
34
34
  6. Every change must still load: `agents.yaml` is validated strictly at load,
35
35
  and `zen check` says so before a model is ever called.
36
36
 
37
+ ### 0.1 Copilot is the meta-agent: CLI and RAG
38
+
39
+ Copilot is always the meta-agent that creates and changes target projects. Load
40
+ the editor's `zen-cli` skill before deciding how a target project should run,
41
+ be validated, or use command-line capabilities. It is the command contract; do
42
+ not reconstruct `zen` arguments from memory or call `--help` to discover them.
43
+
44
+ First determine whether the target project uses RAG: a `zen rag` command in an
45
+ agent prompt or skill, RAG tools supplied by its host, or a documentation or
46
+ schema index it is expected to consult all count. When it does, also load the
47
+ `zen-cli` skill's `references/rag.md` before writing the target's instructions.
48
+
49
+ Then create or maintain `agents/skills/rag_search/SKILL.md` in the target
50
+ project. Its description must say when an agent should load it. Its body must
51
+ name the target's actual RAG surface and index location, state which agents may
52
+ use it, and give an executable retrieval procedure:
53
+
54
+ 1. Discover document and section names with the exact listing command or tool.
55
+ 2. Start with hybrid search for a complete natural-language question.
56
+ 3. Narrow by document, section, and content kind before repeatedly rewording a
57
+ query; verify facts with exact lookup or a verbatim read.
58
+ 4. Use full-text search for a known literal term, vector search for unknown
59
+ wording, and split text/vector queries only when the exact term is
60
+ distinctive enough to improve the semantic question.
61
+ 5. Retrieve a complete section verbatim, or list a table and read its inclusive
62
+ line range when every row is needed.
63
+
64
+ Keep this as runtime guidance, not a pointer to this editor reference: a target
65
+ agent cannot read `.github/` or this project layout while it runs. Include only
66
+ the commands, tools, paths, and corpus-specific constraints that exist in the
67
+ target project. Grant the agents that need CLI RAG the necessary `sandbox:*`
68
+ tool access; give tool-based RAG users the configured RAG tools instead. Run
69
+ `zen check` after adding or changing the skill.
70
+
37
71
  ---
38
72
 
39
73
  ## 1. Mental model
@@ -49,8 +83,9 @@ An agent is four things and nothing more:
49
83
  | **Tools** | `agents.yaml` → `tools:` | What it can _do_ |
50
84
  | **Knowledge** | `agents/skills/*` + memory | What it can _know_, on demand |
51
85
 
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).
86
+ Plus two relations: **handoffs** - which other agents it may transfer control to
87
+
88
+ - and **fork** - whether it may split into parallel branches at all (§6.4).
54
89
 
55
90
  There is no hidden orchestration layer. If behaviour is wrong, one of those six
56
91
  things is wrong.
@@ -93,8 +128,8 @@ is deciding **what is in that sequence and in what order**.
93
128
  Two consequences that drive nearly every design rule in this document:
94
129
 
95
130
  - **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
131
+ mid-run _after_ the first reply - a tool schema, an instruction, a late skill
132
+ activation - invalidates the cache from that point on. This is why tool schemas
98
133
  are fixed at load and skill-owned tools are declared from turn 0 and merely
99
134
  _gated_ at call time.
100
135
  - **Everything in the prefix is paid for on every call.** A 4 000-token prompt
@@ -110,7 +145,7 @@ Two consequences that drive nearly every design rule in this document:
110
145
  a number.
111
146
  - Numbers come from tools; the model narrates, it does not compute.
112
147
  - 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.
148
+ stop") - an unstated failure mode is an invented one.
114
149
 
115
150
  ---
116
151
 
@@ -120,11 +155,11 @@ Two consequences that drive nearly every design rule in this document:
120
155
 
121
156
  ```
122
157
  my-project/
123
- ├── .github/ the editor's brief — §2.4
158
+ ├── .github/ the editor's brief - §2.4
124
159
  │ ├── copilot-instructions.md this file
125
160
  │ ├── prompts/*.prompt.md tasks you invoke by name
126
161
  │ └── skills/*/SKILL.md reference the editor loads on demand
127
- ├── .env credentials — NEVER committed
162
+ ├── .env credentials - NEVER committed
128
163
  ├── INSTRUCTIONS.md house rules, prepended to every agent
129
164
  ├── agents.yaml who exists, what they may reach for
130
165
  ├── agents/
@@ -139,10 +174,10 @@ my-project/
139
174
  │ │ └── SKILL.md
140
175
  │ └── refund_policy/
141
176
  │ ├── SKILL.md
142
- │ └── scripts/calculate.py run at /skills/refund_policy/scripts — §3.4.1
177
+ │ └── scripts/calculate.py run at /skills/refund_policy/scripts - §3.4.1
143
178
  ├── 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
179
+ ├── memory/ the graph agents remember into - §5.3
180
+ ├── sandbox/Dockerfile the image commands run in - §3.7
146
181
  └── sessions/ run state, whatever the agent wrote
147
182
  ```
148
183
 
@@ -152,26 +187,26 @@ The config is found by name, in order: `agents.yaml`, `agents.yml`,
152
187
 
153
188
  ### 2.2 Variants
154
189
 
155
- **Single agent, knowledge-heavy** — the most under-used shape. One agent, one
190
+ **Single agent, knowledge-heavy** - the most under-used shape. One agent, one
156
191
  prompt, a large skill catalog. Prefer this until routing is genuinely needed.
157
192
 
158
193
  ```
159
194
  INSTRUCTIONS.md · agents.yaml · agents/prompts/assistant.md · agents/skills/** (20 skills)
160
195
  ```
161
196
 
162
- **Router + specialists** — a cheap intake agent that classifies and hands off.
197
+ **Router + specialists** - a cheap intake agent that classifies and hands off.
163
198
 
164
199
  ```
165
200
  agents/prompts/{intake,billing,technical,escalation}.md
166
201
  ```
167
202
 
168
- **Pipeline** — fixed stages, each handing to the next; the last one answers.
203
+ **Pipeline** - fixed stages, each handing to the next; the last one answers.
169
204
 
170
205
  ```
171
206
  research → draft → review
172
207
  ```
173
208
 
174
- **Shared skills across projects** — `skills:` accepts a list, merged into one
209
+ **Shared skills across projects** - `skills:` accepts a list, merged into one
175
210
  catalog:
176
211
 
177
212
  ```yaml
@@ -181,7 +216,7 @@ skills:
181
216
  ```
182
217
 
183
218
  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
219
+ ancestor. Several directories become **one** provider (id `project`) - "which
185
220
  folder is this skill in?" is not a question an author should have to answer.
186
221
 
187
222
  ### 2.3 Naming rules (enforced)
@@ -207,18 +242,18 @@ same reader. Keeping them apart is the single easiest thing to get wrong.
207
242
  | `.github/` | the **editor's** assistant, while you edit | how a project of this kind is built |
208
243
 
209
244
  The `.github/` tree follows the same progressive-disclosure discipline the
210
- agents do, for the same reason — it is a prefix somebody pays for:
245
+ agents do, for the same reason - it is a prefix somebody pays for:
211
246
 
212
247
  - **`copilot-instructions.md`** is always on. Everything in it is loaded for
213
248
  every request in this folder, so it holds only what is true of every task.
214
249
  - **`.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.
250
+ `description` matches what you asked. Put long, occasional material here -
251
+ a command surface, a vendor's quirks, a format spec - not in the file above.
217
252
  The `description` is the routing key; §3.4 applies to these as much as to the
218
253
  project's own skills.
219
254
  - **`.github/prompts/<name>.prompt.md`** is a task you invoke by name (`/name`),
220
255
  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
256
+ job is done repeatedly and has a right order - adding an agent, adding a
222
257
  skill, reviewing before a commit.
223
258
 
224
259
  `zen init` and `zen open` rewrite this whole tree from the version of `zen` in
@@ -232,15 +267,15 @@ it. Its entire world is the tools it was granted (§3.5) and four paths:
232
267
 
233
268
  | Path | Is | Access |
234
269
  | ------------ | ----------------------------------------- | ------------------------------------------------------ |
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 |
270
+ | `/workspace` | the session's workspace - the work itself | read/write; `zen run --read-only` withholds the writes |
271
+ | `/assets` | `assets/`, reference material - §3.11 | read-only |
272
+ | `/skills` | the whole skill catalog - §3.4.1 | read-only |
273
+ | `/memory` | files carried by remembered nodes - §5.3 | read-only, and only when `memory:` is configured |
239
274
 
240
275
  Commands run inside the container `sandbox:` describes (§3.7): the same
241
276
  `/workspace`, and whatever the image ships.
242
277
 
243
- Everything else in the project directory is **invisible at run time** —
278
+ Everything else in the project directory is **invisible at run time** -
244
279
  `agents.yaml`, `INSTRUCTIONS.md`, `agents/prompts/`, `agents/skills/` under that
245
280
  name, `sandbox/Dockerfile`, `.env`, `.github/`, `sessions/`, and the project
246
281
  root itself. `zen` reads them to build the run; they are never a path an agent
@@ -249,7 +284,7 @@ can open.
249
284
  Two consequences:
250
285
 
251
286
  - **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
287
+ folder, and is usually some other repository entirely - `--workspace`, or the
253
288
  directory the user was standing in. A prompt saying "read `agents.yaml`" or
254
289
  "the rules are in `agents/skills/`" names a file that is not there.
255
290
  - **Write the mount path, never the repository path.**
@@ -259,9 +294,9 @@ Two consequences:
259
294
  the one place the file is not.
260
295
 
261
296
  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.
297
+ prose**, in the prompt - never sent to read the file that configured it.
263
298
 
264
- #### Checklist — no path an agent cannot open
299
+ #### Checklist - no path an agent cannot open
265
300
 
266
301
  Run this over `INSTRUCTIONS.md`, every `agents/prompts/*.md` and every
267
302
  `agents/skills/*/SKILL.md` after editing any of them. Every path-shaped token in
@@ -276,19 +311,19 @@ those files is read by a model as an instruction to open something:
276
311
  `/home/<someone>`), no `/etc`, `/var` or `/tmp` expected to hold anything
277
312
  the project put there.
278
313
  - [ ] 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).
314
+ committed there - `zen check` lists what every skill folder ships (§3.4.1).
280
315
  - [ ] A skill referring to its own sibling file writes the full
281
316
  `/skills/<name>/` prefix, not `./rates.csv`: the body is rendered into a
282
317
  prompt, not executed from its folder.
283
318
  - [ ] `/memory` is named only in prompts for agents that have `memory:` (§5.3),
284
319
  and `/assets` only where an `assets/` directory exists.
285
- - [ ] The agent holding the prompt has the tool the path implies — `read_file`
320
+ - [ ] The agent holding the prompt has the tool the path implies - `read_file`
286
321
  to open one, `sandbox:*` to run one (§3.4.1).
287
322
  - [ ] Every instruction that opens a file states what to do when it is not there
288
323
  (§4.2, rule 6). Workspaces differ between runs; absence is normal.
289
324
  - [ ] Relative paths appear only where `/workspace`-relative is what was meant.
290
325
 
291
- Finding the candidates, from the project root — every path-shaped token in the
326
+ Finding the candidates, from the project root - every path-shaped token in the
292
327
  three trees, minus the ones under a mount:
293
328
 
294
329
  ```sh
@@ -307,20 +342,20 @@ sentence, `and/or`) or a path the agent cannot open. There is no third case.
307
342
  ### 3.1 `agents.yaml`
308
343
 
309
344
  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 — …`.
345
+ ignored. Errors name the path - `agents.yaml: agents[1].skills.discovery - …`.
311
346
 
312
347
  ```yaml
313
348
  version: 1 # schema version, defaults to 1
314
349
  default: intake # entry agent; wins over any `default: true`
315
350
 
316
351
  providers: {} # named connections (credentials + endpoint)
317
- provider: openai # the provider an unprefixed model id belongs to — §7.3
352
+ provider: openai # the provider an unprefixed model id belongs to - §7.3
318
353
  models: {} # named model configurations
319
354
  model: fast # fallback for agents that do not pin their own
320
- embeddings: {} # named vectorisers — §3.1.1
355
+ embeddings: {} # named vectorisers - §3.1.1
321
356
  embedding: small # the one `AgentProject.embedder()` returns when asked for no name
322
357
  skills: agents/skills # one directory, or a list
323
- assets: assets # reference material, read-only at /assets — §3.11
358
+ assets: assets # reference material, read-only at /assets - §3.11
324
359
 
325
360
  agents: # the only required key; at least one entry
326
361
  - name: intake
@@ -339,13 +374,13 @@ agents: # the only required key; at least one entry
339
374
  | `description` | What a sibling's `transfer_to_<name>` tool tells the model. Write it _for the model_ |
340
375
  | `system` | Prompt path, relative to root. Defaults to `agents/prompts/<name>.md` if present |
341
376
  | `model` | A `models:` alias or shorthand. Falls back to top-level `model:` |
342
- | `tools` | Selectors over the tools `zen run` provides — see §3.6 |
377
+ | `tools` | Selectors over the tools `zen run` provides - see §3.6 |
343
378
  | `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 |
379
+ | `skills` | Skill binding - see §5.2 |
380
+ | `fork` | `true`, or `{ agents, maxBranches }` - opt-in to parallel branches; see §6.4 |
346
381
  | `default` | `true` marks the entry point when no top-level `default:` |
347
382
 
348
- **Providers** — a provider is a _connection_, not a model. One client is built
383
+ **Providers** - a provider is a _connection_, not a model. One client is built
349
384
  per name and shared, so five agents on one key open one connection pool.
350
385
 
351
386
  | Field | Meaning |
@@ -353,20 +388,20 @@ per name and shared, so five agents on one key open one connection pool.
353
388
  | `kind` | `openai` \| `google` \| `vertex` \| `anthropic` \| `openrouter` \| `openai-compatible` (default `openai`) |
354
389
  | `apiKey` / `apiKeyEnv` | Literal, `${VAR}`, or the name of the env var holding it |
355
390
  | `baseURL` / `baseURLEnv` | For gateways and compatible endpoints |
356
- | `project` / `location` | **vertex only** — GCP project id and region (or `global`) |
391
+ | `project` / `location` | **vertex only** - GCP project id and region (or `global`) |
357
392
  | `headers` | Sent on every request: routing, attribution, api versions |
358
393
  | `timeoutMs`, `maxRetries` | Per-request timeout and retry count |
359
394
 
360
395
  `openai`, `google`, `vertex`, `anthropic`, `openrouter` and `openai-compatible`
361
396
  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
397
+ `providers:` entry only when it says something the default does not - a second
363
398
  key, a region, a base url.
364
399
 
365
400
  `${VAR}` and `${VAR:-fallback}` expand from the environment, compose inside
366
401
  longer values (`https://${GATEWAY}/v1`), and are **lazy**: a declared-but-unused
367
402
  provider with a missing key does not fail loading.
368
403
 
369
- **Errors caught at load** — rely on these instead of defensive checks:
404
+ **Errors caught at load** - rely on these instead of defensive checks:
370
405
 
371
406
  - any unknown key; any name breaking the pattern
372
407
  - `models.<alias>.provider` naming an undeclared provider
@@ -378,24 +413,24 @@ provider with a missing key does not fail loading.
378
413
  - `agents[].fork.agents` naming an unknown agent, or being empty; `maxBranches` below 2
379
414
  - `system:` pointing at a missing file, or outside the project root
380
415
 
381
- **Not caught at load** — a model id whose prefix is missing and so resolves to the
416
+ **Not caught at load** - a model id whose prefix is missing and so resolves to the
382
417
  wrong provider (§7.3), and any combination of knobs the vendor rejects at request
383
418
  time, such as OpenAI reasoning on chat completions (§7.6). Both surface on the
384
419
  first call, so read §7 before writing a `models:` entry.
385
420
 
386
- **Caught by `zen check`, not by the loader** — a handoff with no path back
421
+ **Caught by `zen check`, not by the loader** - a handoff with no path back
387
422
  (`handoff.one-way`, §6.3). The project runs; it just cannot return.
388
423
 
389
- **Comments in `agents.yaml`** — this file is the architecture diagram of the
424
+ **Comments in `agents.yaml`** - this file is the architecture diagram of the
390
425
  project, and its comments are read by whoever has to change it next. Write them
391
426
  at that level:
392
427
 
393
428
  - Say **what a block is for** and **why it exists**: what this agent owns, why
394
429
  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.
430
+ - Keep them **short** - one line above a block, a few words at the end of a line.
396
431
  A comment longer than the thing it describes is a design doc in the wrong file.
397
432
  - 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
433
+ is appended, how prompt caching works, what the loader validates - none of that
399
434
  belongs here. It is documented in this file and in `docs/agents-yaml.md`.
400
435
  - Do **not** restate the key. `# the model this agent uses` above `model:` is
401
436
  noise; `# cheap: it only classifies` is not.
@@ -404,7 +439,7 @@ at that level:
404
439
  ```yaml
405
440
  # Intake classifies and routes; it never answers.
406
441
  - name: intake
407
- model: router # cheap tier — one sentence in, one handoff out
442
+ model: router # cheap tier - one sentence in, one handoff out
408
443
  handoffs: [adjuster, escalation]
409
444
  ```
410
445
 
@@ -432,12 +467,12 @@ embedding: small
432
467
  | Field | Meaning |
433
468
  | ------------------------ | ------------------------------------------------------------------------ |
434
469
  | `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 |
470
+ | `model` | **Required.** The bare id - the object form never re-parses a shorthand |
436
471
  | `dimensions` | Truncate to this width, where the model supports it |
437
472
  | `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 |
473
+ | `title` | **gemini only** - a document title the retrieval task type weighs |
474
+ | `maxBatch` | **gemini only** - texts per request; see below |
475
+ | `routing` | **openrouter only** - which upstream provider serves the request |
441
476
 
442
477
  Four things differ from `models:` and are worth knowing before you write one:
443
478
 
@@ -445,14 +480,14 @@ Four things differ from `models:` and are worth knowing before you write one:
445
480
  api means nothing on any protocol. The shorthand is `[provider:]model`.
446
481
  - **`kind: anthropic` has no embeddings API at all.** Anthropic publishes none
447
482
  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
483
+ fails at load. Use another provider - the connection need not be the one the
449
484
  agents talk through.
450
485
  - **Not a per-agent key.** Nothing in the runtime consumes a vectoriser yet, so
451
486
  there is no `agents[].embedding:`. A TypeScript host reaches one with
452
487
  `project.embedder()` for the default, or `project.embedder('large')` by name.
453
488
  - **Vectors come back unit length**, so cosine and dot product agree. This is a
454
489
  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,
490
+ raw slice and only some models rescale afterwards - `gemini-embedding-2` does,
456
491
  `gemini-embedding-001` returns |v| ≈ 0.58 at 768 of its 3072. Pass
457
492
  `normalize: false` on a call to see what the model actually said.
458
493
 
@@ -468,7 +503,7 @@ credential each one would need.
468
503
 
469
504
  House rules, read **once** and prepended to every agent's system prompt. It is
470
505
  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
506
+ deliberately not `AGENTS.md` - that one belongs to the coding assistant reading
472
507
  this file, and these rules address the project's own agents.
473
508
 
474
509
  Put here only what is true for **every** agent:
@@ -482,11 +517,11 @@ Do **not** put here: anything one agent needs and another does not; anything tha
482
517
  changes weekly; long reference data (that is a skill).
483
518
 
484
519
  **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
520
+ It is not a README and not a design document - every line is paid for on every
486
521
  call, by every agent, and each one is an instruction the model will try to
487
522
  follow. So:
488
523
 
489
- - It is a **prompt**: §4.2 applies in full — second person, imperative, no
524
+ - It is a **prompt**: §4.2 applies in full - second person, imperative, no
490
525
  hedging, no meta-talk about the runtime, failure paths stated.
491
526
  - Carry the **shared model of the project**: what this system is, what the agents
492
527
  are collectively for, how the work flows between them, and the vocabulary and
@@ -513,7 +548,7 @@ Structure that works:
513
548
 
514
549
  <what it does, as a short numbered procedure or 3–5 rules>
515
550
 
516
- <what it must NOT do — especially the neighbouring agent's job>
551
+ <what it must NOT do - especially the neighbouring agent's job>
517
552
 
518
553
  <how to finish: the shape of the answer, or which handoff ends the turn>
519
554
  ```
@@ -530,7 +565,7 @@ Your job is small and you should finish it fast:
530
565
  and see what it covers.
531
566
  3. Hand the case to the `adjuster` agent, which owns the peril policies.
532
567
 
533
- Do not quote coverage rules yourself — you do not have them. Do not ask the
568
+ Do not quote coverage rules yourself - you do not have them. Do not ask the
534
569
  claimant for anything the message already contains.
535
570
  ```
536
571
 
@@ -538,7 +573,7 @@ Note what makes it work: it is 9 lines; it names the tool and the handoff
538
573
  literally; it states the boundary ("you do not have them") with the _reason_; it
539
574
  forbids the specific failure that agent actually exhibits.
540
575
 
541
- Write it as a prompt, under the rules in §4.2 — **instructive and concise**. It
576
+ Write it as a prompt, under the rules in §4.2 - **instructive and concise**. It
542
577
  tells one agent what to do; it never explains the system (`INSTRUCTIONS.md`
543
578
  already did, §3.2), never repeats a house rule, and never describes the runtime.
544
579
  Every line should be an instruction the model can act on or a boundary it can
@@ -549,8 +584,8 @@ yet.
549
584
 
550
585
  ### 3.4 Skills
551
586
 
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.
587
+ A skill is curated, reusable instruction content - plus the files and tools it
588
+ needs - loaded **on demand** instead of permanently occupying the system prompt.
554
589
  One layout, and only one:
555
590
 
556
591
  ```
@@ -564,7 +599,7 @@ to move it to `refund_policy/SKILL.md`. A skill with nowhere to put a table, an
564
599
  example or a script can only ever be prose, which is the half of the idea that
565
600
  does not need a file.
566
601
 
567
- Frontmatter is a deliberately small subset of YAML — `key: value`, plus `[a, b]`
602
+ Frontmatter is a deliberately small subset of YAML - `key: value`, plus `[a, b]`
568
603
  flow lists for `tags` and `tools`. **Every key is optional**: `name` defaults to
569
604
  the folder name, `description` to the first non-empty line of the body.
570
605
 
@@ -579,9 +614,9 @@ tools: [issue_refund]
579
614
 
580
615
  A parcel counts as late once it passes its promised delivery date by 48 hours.
581
616
 
582
- - **48h to 7 days late** — apologise, confirm the parcel is still moving, and
617
+ - **48h to 7 days late** - apologise, confirm the parcel is still moving, and
583
618
  refund the shipping fee. Do not refund the goods.
584
- - **More than 7 days late** — treat the parcel as lost. Offer a replacement at
619
+ - **More than 7 days late** - treat the parcel as lost. Offer a replacement at
585
620
  no cost, or a full refund, whichever the customer prefers.
586
621
 
587
622
  Never give a new delivery date. Say "still in transit" instead.
@@ -594,7 +629,7 @@ skill is needed_, not as a title:
594
629
  | Bad | Good |
595
630
  | --------------------------- | ------------------------------------------------------------------------------------ |
596
631
  | `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.` |
632
+ | `description: Water policy` | `description: Escape of water from plumbing and tanks - and the freezing exclusion.` |
598
633
 
599
634
  **`tools:` in frontmatter** names skill-owned tools. They are declared to the
600
635
  provider from turn 0 (the schema never changes) but **refuse to execute** until
@@ -635,7 +670,7 @@ Rules that follow from the mount:
635
670
  - The agent needs `sandbox:*` to run the script and `read_file` to open a data
636
671
  file. Grant them, or the skill's own instructions cannot be followed (§3.5).
637
672
  - **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 —
673
+ script needs `pandas` is a `sandbox/Dockerfile` line that was never written -
639
674
  not a first line that runs `pip install` on every turn (§3.7).
640
675
  - **`/skills` is read-only.** A script that produces a file must take an output
641
676
  path and write it under `/workspace`.
@@ -644,7 +679,7 @@ Rules that follow from the mount:
644
679
  directory is `/workspace`.
645
680
  - Say what to do when the script fails, as with any other instruction (§4.2).
646
681
  - With several `skills:` directories, one catalog is the whole of `/skills` and
647
- several take `/skills/<folder>` each — check the rendered path before writing
682
+ several take `/skills/<folder>` each - check the rendered path before writing
648
683
  it into the body.
649
684
  - The mount is created before anything is loaded, so `allow:` limits what an
650
685
  agent can **load**, not what it can **read**. Do not put anything in the
@@ -656,8 +691,8 @@ committed shows up as a skill with no files.
656
691
  ### 3.5 Tools
657
692
 
658
693
  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
694
+ groups - the workspace tools (§3.6), the sandbox tools (§3.7) and the web tools
695
+ (§3.8) - and `agents.yaml` decides which agent holds which. Nothing else reaches
661
696
  the machine, so `tools:` is the whole permission model: an agent that does not
662
697
  name a tool cannot use it, whatever its prompt says.
663
698
 
@@ -668,7 +703,7 @@ Two rules follow:
668
703
  - **Say in the prompt when to reach for what.** A granted tool the prompt never
669
704
  mentions is used at the model's discretion, which is not the same as never.
670
705
 
671
- Skills can own tools too — `tools:` in a skill's frontmatter (§3.4) names tools
706
+ Skills can own tools too - `tools:` in a skill's frontmatter (§3.4) names tools
672
707
  that refuse to run until that skill is active.
673
708
 
674
709
  ### 3.6 The workspace tools (`workspace:*`)
@@ -687,12 +722,12 @@ them cannot see a file at all.
687
722
  | `move_file` | Moves or renames; refuses to clobber without `overwrite` |
688
723
  | `delete_file` | Deletes; a directory needs `recursive` |
689
724
 
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
725
+ Every path is resolved through one containment gate - symlinks followed, then
726
+ checked - so nothing outside the workspace root is reachable. Reads are capped
692
727
  and listings bounded, so no single call can flood the context.
693
728
 
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
729
+ Paths are written relative to the root, and `/workspace/...` - the name the same
730
+ directory has inside the sandbox (§3.7) - is accepted as well, so a path copied
696
731
  out of a command's output does not have to be translated first. When a mount is
697
732
  configured the tools also _report_ that name, so the two toolsets speak one
698
733
  vocabulary and a path can be passed from either to the other unchanged; with no
@@ -720,7 +755,7 @@ agents:
720
755
 
721
756
  Selectors apply in the order written, so a `-` line reads as an exception to the
722
757
  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 —
758
+ refuses the file. `workspace:*` needs no quoting. There is no name globbing -
724
759
  `read_*` is an unknown tool, because a selector should track a declared set, not
725
760
  a naming habit.
726
761
 
@@ -730,7 +765,7 @@ asks for: the deployment overriding the repository, as everywhere else.
730
765
  **Prompting for them.** Three lines earn their place in any prompt that grants
731
766
  this group:
732
767
 
733
- - Read before editing — `apply_patch` matches on exact text, so a patch built
768
+ - Read before editing - `apply_patch` matches on exact text, so a patch built
734
769
  from memory fails.
735
770
  - Prefer `apply_patch` to `write_file` for an existing file. Rewriting a file to
736
771
  change one line costs the whole file in output tokens and loses everything the
@@ -751,7 +786,7 @@ build, test, install and inspect rather than only read and write.
751
786
  | `read_command_output` | A window of a job's output from `start_line`, plus whether it still runs |
752
787
  | `stop_command` | Signals a job's process group |
753
788
 
754
- The container is the boundary. Nothing inspects the command — there is no
789
+ The container is the boundary. Nothing inspects the command - there is no
755
790
  allow-list of binaries and no pattern matching on what the model wrote, because
756
791
  both are trivially defeated and neither survives a shell. What holds is that
757
792
  only the workspace and the session's `/home/agent` are mounted, the container
@@ -759,12 +794,12 @@ is removed at the end of the session, and the command is never a shell argument
759
794
  on the host: it travels on stdin to `/bin/sh` inside.
760
795
 
761
796
  **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
797
+ container. Every field has a default, so the block is optional - write only the
763
798
  lines that differ:
764
799
 
765
800
  ```yaml
766
801
  sandbox:
767
- persist: true # recommended — see below
802
+ persist: true # recommended - see below
768
803
  image: docker.io/library/python:3.14-slim-bookworm # the default
769
804
  cpus: 4 # fractional cores
770
805
  memory: 4096 # MiB
@@ -783,15 +818,15 @@ sandbox:
783
818
  | `workdir` | `/workspace` | Mount point and default cwd |
784
819
  | `timeout` | `120` | Seconds per command |
785
820
  | `user` | the image's | uid, name, or `uid:gid` |
786
- | `persist` | `false` — **set it to `true`** | Keep the container between runs of a session |
821
+ | `persist` | `false` - **set it to `true`** | Keep the container between runs of a session |
787
822
  | `env` | none | Host variables to forward, **names** |
788
823
 
789
- `env:` takes **names, never values** — a value here would be a secret in the
790
- repository — and anything credential-shaped (`KEY`, `TOKEN`, `SECRET`,
824
+ `env:` takes **names, never values** - a value here would be a secret in the
825
+ repository - and anything credential-shaped (`KEY`, `TOKEN`, `SECRET`,
791
826
  `PASSWORD`, `CREDENTIAL`) is refused at load.
792
827
 
793
828
  **`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`
829
+ or a pinned toolchain, names a Dockerfile instead of an image - `zen init`
795
830
  writes one at `sandbox/Dockerfile` with Python and Node in it:
796
831
 
797
832
  ```yaml
@@ -802,7 +837,7 @@ sandbox:
802
837
  # context: . # what the build may COPY from; the Dockerfile's folder by default
803
838
  ```
804
839
 
805
- `image:` and `build:` cannot both be set — a Dockerfile names its own base in
840
+ `image:` and `build:` cannot both be set - a Dockerfile names its own base in
806
841
  its `FROM` line. The tag is a hash of the Dockerfile and its context, so
807
842
  editing it produces a new image and, like any other change here, a new
808
843
  container. `zen check` builds it and runs a command in it, so a Dockerfile that
@@ -834,12 +869,12 @@ agents:
834
869
 
835
870
  **Write `persist: true` unless you have a reason not to.** By default the
836
871
  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
872
+ `/workspace`, and `/home/agent` - which is `$HOME` inside, backed by the session
838
873
  directory. That covers `pip install --user`, `npm config` and `~/.cache`, but it
839
874
  does **not** cover the ordinary thing an agent actually does: `pip install X` or
840
875
  `apt-get install X` as root writes to the container's system paths, and those
841
876
  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
877
+ single time - and usually does not realise it has, because the previous run's
843
878
  transcript says it succeeded.
844
879
 
845
880
  ```yaml
@@ -857,7 +892,7 @@ Changing any field renames the container, so bumping the image gets a fresh one
857
892
  rather than an old one quietly persisting with the wrong contents. That is also
858
893
  the one sharp edge of `persist: true`: a config change abandons the old
859
894
  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.
895
+ in `image:` - or in `sandbox/Dockerfile` - rather than in an accumulated rootfs.
861
896
 
862
897
  Granting the group is what makes the project need Podman: `zen run` checks the
863
898
  engine before the first turn and exits `5` with an install command if it is
@@ -867,15 +902,15 @@ reference: `docs/agents-yaml.md`.
867
902
  **Prompting for them.** Two lines earn their place:
868
903
 
869
904
  - 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
905
+ see - an edit made with `apply_patch` is what a command will compile, and a
871
906
  path from either side works on both.
872
- - Anything that does not return, returns — use `run_command_background` for a
907
+ - Anything that does not return, returns - use `run_command_background` for a
873
908
  server, not `run_command` with a large timeout.
874
909
 
875
910
  ### 3.8 The web tools (`exa:*`)
876
911
 
877
912
  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,
913
+ [Exa](https://exa.ai) - a search index built for models rather than for people,
879
914
  so a query is a sentence describing what is wanted, not a bag of keywords.
880
915
 
881
916
  | Tool | What it does |
@@ -886,7 +921,7 @@ so a query is a sentence describing what is wanted, not a bag of keywords.
886
921
 
887
922
  The three are meant to be used in that order: **search to find, read to quote.**
888
923
  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.
924
+ from - `web_search` returns the sentences that made a page match, not the page.
890
925
  `web_answer` runs a search _and_ a model on the other side, so it is the slowest
891
926
  and dearest of the three; it earns its cost when the answer is a fact spread
892
927
  over several pages, and wastes it when a specific document is wanted.
@@ -903,7 +938,7 @@ agents:
903
938
 
904
939
  **The key.** All three read `$EXA_API_KEY` **when they are called**, not when the
905
940
  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
941
+ key - the tools simply refuse, on the turn that tried, saying which variable is
907
942
  missing. Get a key from <https://dashboard.exa.ai/api-keys> and hold it in
908
943
  either place:
909
944
 
@@ -922,14 +957,14 @@ project is still valid, it just cannot search yet.
922
957
  | Argument | On | Why it exists |
923
958
  | ------------------------------------------ | ------------ | ------------------------------------------------------------------------------- |
924
959
  | `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 |
960
+ | `include_domains` / `exclude_domains` | `web_search` | The replacement for `site:` - operators in the query text do not work here |
926
961
  | `start_published_date` / `end_published_…` | `web_search` | ISO 8601. The only reliable way to exclude a stale answer |
927
962
  | `category` | `web_search` | `company`, `publication`, `news`, `personal site`, `financial report`, `people` |
928
963
  | `max_characters` | `web_read` | 4 000 by default, 10 000 at most; `truncated` says when a page was cut |
929
964
  | `max_age_hours` | `web_read` | `0` forces a live crawl. Omit it unless the page changes by the hour |
930
965
 
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
966
+ Every reply is bounded - pages are cut at the cap and the whole call at 128 KiB
967
+ of text - so one call cannot flood the context. Each carries `cost_usd`, which
933
968
  is what the vendor charged for that call.
934
969
 
935
970
  A failure is **reported, not raised**: a refused key, an exhausted balance, a
@@ -940,7 +975,7 @@ did load, so one bad link does not lose the rest.
940
975
  **Prompting for them.** Three lines earn their place in any prompt granting this
941
976
  group:
942
977
 
943
- - Search with a sentence, not keywords — the query is read by a model.
978
+ - Search with a sentence, not keywords - the query is read by a model.
944
979
  - Never quote an excerpt. `web_search` says which page to open; `web_read` says
945
980
  what it contains.
946
981
  - Say when the web is allowed to override what the model already believes, and
@@ -965,7 +1000,7 @@ zen run --read-only withhold every tool that can write
965
1000
 
966
1001
  The project is read **once, up front**: a missing prompt, an unknown tool name,
967
1002
  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
1003
+ named. Flags always win over the file - the repository states intent, the
969
1004
  invocation overrides it.
970
1005
 
971
1006
  A session owns a workspace, a trajectory, memory and whatever the agent wrote,
@@ -980,17 +1015,17 @@ GOOGLE_APPLICATION_CREDENTIALS=./.keys/vertex.json
980
1015
  EXA_API_KEY=...
981
1016
  ```
982
1017
 
983
- Never commit. Never inline a key into `agents.yaml` — use `${VAR}`. Never print a
1018
+ Never commit. Never inline a key into `agents.yaml` - use `${VAR}`. Never print a
984
1019
  key in a log line, a test fixture, or a chat message.
985
1020
 
986
1021
  ### 3.11 `assets/`
987
1022
 
988
- `assets/` next to `agents.yaml` — or `assets: <path>` — is reference material
1023
+ `assets/` next to `agents.yaml` - or `assets: <path>` - is reference material
989
1024
  every agent can read and none can write. It is mounted at `/assets`: the file
990
1025
  tools read, list and search it, `run_command` sees it bind-mounted read-only,
991
1026
  and every tool that would change it refuses.
992
1027
 
993
- Put a handbook, a specification, a schema or a style guide there — what agents
1028
+ Put a handbook, a specification, a schema or a style guide there - what agents
994
1029
  consult while working, as opposed to the workspace, which is the work. Do not
995
1030
  point `assets:` at the project root: that hands every agent the `sessions/`
996
1031
  directory, every transcript of every run, including the one reading it.
@@ -1008,7 +1043,7 @@ is a different project, not a different key.
1008
1043
  ```
1009
1044
  1. INSTRUCTIONS.md (shared, once, all agents)
1010
1045
  2. agents/prompts/<n>.md (this agent)
1011
- 3. skill index (rendered by the runtime — do not hand-write it)
1046
+ 3. skill index (rendered by the runtime - do not hand-write it)
1012
1047
  4. preloaded skills (activated before the first call)
1013
1048
  ```
1014
1049
 
@@ -1022,7 +1057,7 @@ stale silently).
1022
1057
  reference." Not "The agent should be able to look up policies."
1023
1058
  2. **Name things literally.** Tools in backticks by their exact name; agents by
1024
1059
  their exact `name:`. The model matches strings.
1025
- 3. **State the boundary and the reason.** "Do not quote coverage rules — you do
1060
+ 3. **State the boundary and the reason.** "Do not quote coverage rules - you do
1026
1061
  not have them." A prohibition without a reason gets rationalised away.
1027
1062
  4. **Prefer prohibitions that are specific.** "Never say 'approved'" beats "be
1028
1063
  careful about commitments".
@@ -1045,7 +1080,7 @@ stale silently).
1045
1080
 
1046
1081
  Write like a competent colleague briefing a new hire on their first day: short
1047
1082
  sentences, concrete nouns, the reason behind each rule. The model mirrors the
1048
- register it is given — a prompt written in bureaucratic hedging produces
1083
+ register it is given - a prompt written in bureaucratic hedging produces
1049
1084
  bureaucratic hedging.
1050
1085
 
1051
1086
  ### 4.4 Debugging a prompt
@@ -1093,28 +1128,28 @@ agents:
1093
1128
  maxIndexEntries: 20 # cap the rendered index
1094
1129
  ```
1095
1130
 
1096
- - **`index`** — names and descriptions rendered into the system prompt. The
1131
+ - **`index`** - names and descriptions rendered into the system prompt. The
1097
1132
  default and right answer for most catalogs.
1098
- - **`search`** — no index, only a `skill_search` tool. For catalogs too large to
1133
+ - **`search`** - no index, only a `skill_search` tool. For catalogs too large to
1099
1134
  render.
1100
- - **`none`** — preloads only.
1101
- - **`preload`** — for content there is no case for the model to decline: house
1135
+ - **`none`** - preloads only.
1136
+ - **`preload`** - for content there is no case for the model to decline: house
1102
1137
  tone, a formatting contract. Making it choose is a wasted round trip.
1103
1138
  Preloaded skills are filtered _out_ of the rendered index, and their activation
1104
1139
  lands at the head of the transcript, inside the cached prefix.
1105
- - **`allow`** — how one catalog serves several agents without each seeing all of
1140
+ - **`allow`** - how one catalog serves several agents without each seeing all of
1106
1141
  it. A `preload` entry must also be in `allow`.
1107
1142
 
1108
1143
  ### 5.3 Memory
1109
1144
 
1110
1145
  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
1146
+ a **graph** - a node is one thing worth keeping, an edge says how one led to
1147
+ another - so recall returns the connected piece rather than a ranked list, and a
1113
1148
  remembered script arrives with the request that asked for it.
1114
1149
 
1115
1150
  It is configured in `agents.yaml`: a top-level `memory:` block says where the
1116
1151
  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
1152
+ may do with it. It lives with the **project**, not the session - a memory that
1118
1153
  died with the session would be a cache. Recall is automatic on turns that follow
1119
1154
  new user input, which is what makes memory work without the model remembering to
1120
1155
  look.
@@ -1132,18 +1167,18 @@ one graph, masked per agent, never one graph each. The four `memory_*` tools are
1132
1167
  derived from `access` and are never listed in `tools:`.
1133
1168
 
1134
1169
  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
1170
+ persist. Anything authoritative - a rate, a policy clause, a procedure - belongs
1136
1171
  in a skill, where it is versioned and reviewable.
1137
1172
 
1138
- The full treatment — kinds and relations, how ranking and stitching spend their
1173
+ The full treatment - kinds and relations, how ranking and stitching spend their
1139
1174
  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.
1175
+ in front of a `zen rag` index - is in the `zen-memory` skill.
1141
1176
 
1142
1177
  ### 5.4 Cache discipline
1143
1178
 
1144
1179
  Rules that follow directly from "stable prefix = cache hit":
1145
1180
 
1146
- - Do not reorder `agents.yaml` for cosmetic reasons — tool order is prompt order.
1181
+ - Do not reorder `agents.yaml` for cosmetic reasons - tool order is prompt order.
1147
1182
  - Do not put timestamps, run ids, or "today is …" in `INSTRUCTIONS.md` or a
1148
1183
  prompt. A changing prefix is a permanent cache miss. Put volatile facts in a
1149
1184
  tool result.
@@ -1169,10 +1204,10 @@ Split into a second agent when at least one is true:
1169
1204
 
1170
1205
  Do **not** split because:
1171
1206
 
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
1207
+ - The prompt got long - split it into skills instead.
1208
+ - It feels tidier - every handoff costs a full re-render of the system prompt and
1174
1209
  a fresh cache prefix.
1175
- - You want "a planner and an executor" with no distinct tools or models — that is
1210
+ - You want "a planner and an executor" with no distinct tools or models - that is
1176
1211
  one agent with a numbered procedure.
1177
1212
 
1178
1213
  **Start with one agent. Add the second when a specific case forces it.**
@@ -1181,7 +1216,7 @@ Do **not** split because:
1181
1216
 
1182
1217
  **Router + specialists.** A cheap, fast agent whose only job is classification
1183
1218
  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
1219
+ from answering. Every specialist hands back to it - a handoff does not return on
1185
1220
  its own, so without that edge the first routed question is the last one routed.
1186
1221
 
1187
1222
  ```yaml
@@ -1193,11 +1228,11 @@ agents:
1193
1228
  ```
1194
1229
 
1195
1230
  **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
1231
+ user. Encode the order in `handoffs:` so a stage cannot skip ahead - and close
1197
1232
  the loop, by handing back from the last stage to the first.
1198
1233
 
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
1234
+ **Fan-out / join.** For independent parallel work - ten regions, four review
1235
+ lenses, six candidate suppliers - declare `fork:` on the agent that owns the
1201
1236
  work and let the model split it. See §6.4.
1202
1237
 
1203
1238
  **Single agent + rich catalog.** One agent, `discovery: index`, twenty skills.
@@ -1208,11 +1243,11 @@ multi-agent instinct suggests.
1208
1243
 
1209
1244
  - `description:` on the target agent is what the model reads when deciding.
1210
1245
  Write it as a routing condition: _"Applies the written peril policies to a
1211
- claim and explains the outcome."_ — not _"The adjuster agent."_
1246
+ claim and explains the outcome."_ - not _"The adjuster agent."_
1212
1247
  - Handoffs are bare name strings; there is no per-edge configuration.
1213
1248
  - Self-handoff is a load error.
1214
1249
  - **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
1250
+ from `B` back to `A`. Indirect counts - `B → C → A` is a way home - so the
1216
1251
  rule is that every handoff edge lies on a cycle. `zen check` warns
1217
1252
  (`handoff.one-way`) on any edge that does not.
1218
1253
  - Handoff collapses history by policy: the receiving agent sees a selection, not
@@ -1221,7 +1256,7 @@ multi-agent instinct suggests.
1221
1256
 
1222
1257
  **Why the return edge is not optional.** A handoff is a one-way door: control
1223
1258
  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,
1259
+ edge owns the conversation for the rest of the session - the next question,
1225
1260
  whatever it is about, is answered by the specialist the router sent the user to.
1226
1261
  The drawn architecture holds for one turn and then quietly stops being the
1227
1262
  architecture.
@@ -1237,7 +1272,7 @@ interchangeable:
1237
1272
  a handoff spends the conversation to get the answer.
1238
1273
 
1239
1274
  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
1275
+ holding B's answer, so A never sees the steps B took - no tool calls, no files
1241
1276
  read, no intermediate reasoning. That is what makes fanning out cheap, and it is
1242
1277
  what to design around: whatever A will need must be _in_ the answer, so say so in
1243
1278
  B's prompt. Work whose value is the trace rather than the conclusion does not
@@ -1266,43 +1301,43 @@ agents:
1266
1301
 
1267
1302
  The **model** decides the split: it names N branches, each with self-contained
1268
1303
  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
1304
+ tool result** in the parent's history - so N branches cost the parent
1270
1305
  O(N × summary), not O(N × full history).
1271
1306
 
1272
1307
  Rules worth knowing before you write the key:
1273
1308
 
1274
- - `agents:` **may include the forking agent itself** — unlike `handoffs:`, that
1309
+ - `agents:` **may include the forking agent itself** - unlike `handoffs:`, that
1275
1310
  is not an error, and one role fanned out over ten items is the common shape.
1276
1311
  The list reaches the model as an `enum`, so a name outside it cannot even be
1277
1312
  decoded.
1278
1313
  - A fork always needs **at least two** branches. A one-branch call is refused
1279
1314
  with a message telling the model to do the work itself instead.
1280
1315
  - **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.
1316
+ it is a sequence, not a fork - keep it in one conversation.
1282
1317
  - Nesting is capped by the run's `maxForkDepth` (2 by default), so a branch may
1283
1318
  fork again but not without bound.
1284
1319
  - Fork vs handoff: a handoff is _one_ conversation changing owner; a fork is the
1285
1320
  _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
1321
+ does not** - which is why a handoff needs a return path (§6.3) and a fork
1287
1322
  needs nothing.
1288
1323
 
1289
- Choose `context:` deliberately — the model sets it per call, so say in the
1324
+ Choose `context:` deliberately - the model sets it per call, so say in the
1290
1325
  agent's prompt which one this work wants:
1291
1326
 
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
1327
+ - `inherit` - branch gets the full prefix. For work that depends on the case.
1328
+ - `compact` - prefix minus tool calls, tool results and recalls: _what was
1294
1329
  decided_, not the raw noise. **The default for wide fan-outs.**
1295
- - `none` — system prompt plus instructions only. Cheapest; for independent
1330
+ - `none` - system prompt plus instructions only. Cheapest; for independent
1296
1331
  lookups.
1297
1332
 
1298
1333
  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
1334
+ to reach for it, in the terms of this domain - _"When the request covers more
1335
+ than one region, fork one branch per region and merge their findings"_ - or a
1301
1336
  weaker model will work through the list serially and never call it.
1302
1337
 
1303
1338
  ### 6.5 Termination
1304
1339
 
1305
- A turn ends when the model replies with no tool calls — that text is the answer.
1340
+ A turn ends when the model replies with no tool calls - that text is the answer.
1306
1341
  There is no turn limit, so an agent that must stop somewhere needs a prompt that
1307
1342
  says where: what "done" looks like, and what to do when it cannot get there.
1308
1343
 
@@ -1315,7 +1350,7 @@ should contain. An agent with no stated finish keeps working the problem.
1315
1350
 
1316
1351
  ### 7.1 Tiers
1317
1352
 
1318
- Think in three tiers, and give them **role names, not vendor names** — so the
1353
+ Think in three tiers, and give them **role names, not vendor names** - so the
1319
1354
  mapping changes in one place.
1320
1355
 
1321
1356
  | Alias | Role | Reasoning |
@@ -1379,7 +1414,7 @@ Only the **first** colon separates, so a fine-tuned id must name its provider:
1379
1414
  `openai:ft:gpt-4o:acme::a1b2`.
1380
1415
 
1381
1416
  **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
1417
+ hint - nothing reads `gemini-3.5-flash` and infers Google. An unprefixed id goes
1383
1418
  to the default provider, which is `openai` unless a top-level `provider:` says
1384
1419
  otherwise, so a bare `gemini-3.5-flash` asks OpenAI for a Google model and fails
1385
1420
  with `OPENAI_API_KEY is not set`. The message names the provider it resolved to;
@@ -1398,26 +1433,26 @@ over one connection.
1398
1433
 
1399
1434
  | Field | Applies to | Notes |
1400
1435
  | ----------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------- |
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 |
1436
+ | `reasoningEffort` | openai, openrouter | Free string on purpose - the API is the authority on validity. On openai **needs `api: responses`** - §7.6 |
1437
+ | `reasoningSummary` | openai, openrouter | `auto` \| `concise` \| `detailed`. On openai **needs `api: responses`** - §7.6 |
1403
1438
  | `maxTokens` | anthropic, gemini, openrouter | Cap on **output** tokens, not context. Anthropic requires one (default 8192) |
1404
1439
  | `thinkingBudgetTokens` | anthropic | Extended thinking budget |
1405
1440
  | `thinkingBudget` | gemini 2.5 | Tokens: `0` off, `-1` auto |
1406
1441
  | `thinkingLevel` | gemini 3 | `minimal` \| `low` \| `medium` \| `high` |
1407
1442
  | `includeThoughts` | gemini | Thought summaries; default `true` |
1408
- | `routing` / `fallbacks` | openrouter | Upstream provider preferences, and models to fall back to — §7.5 |
1443
+ | `routing` / `fallbacks` | openrouter | Upstream provider preferences, and models to fall back to - §7.5 |
1409
1444
 
1410
1445
  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
1446
+ `api:` exists only for the OpenAI protocol - naming it on a Gemini or Anthropic
1412
1447
  model is an error.
1413
1448
 
1414
1449
  Each vendor's own SDK is used rather than its OpenAI-compatible endpoint, because
1415
1450
  those endpoints drop exactly what this runtime is built on: thinking budgets,
1416
- thought signatures, cache accounting. `openai-compatible` is the shim kind — vLLM,
1451
+ thought signatures, cache accounting. `openai-compatible` is the shim kind - vLLM,
1417
1452
  a self-hosted gateway. `openrouter` used to be that shim with its base url
1418
1453
  (`https://openrouter.ai/api/v1`) and key env (`OPENROUTER_API_KEY`) filled in; it
1419
1454
  now has its own SDK, which is what makes provider routing and fallback chains
1420
- available — §7.5.
1455
+ available - §7.5.
1421
1456
 
1422
1457
  ### 7.5 OpenRouter
1423
1458
 
@@ -1425,7 +1460,7 @@ A gateway: one key and one endpoint in front of several hundred models from
1425
1460
  every vendor. Useful when a project wants to compare families without holding
1426
1461
  four accounts, and when a cheap tier should be swappable by editing one id.
1427
1462
 
1428
- The whole declaration is the kind — `baseURL` and `OPENROUTER_API_KEY` are its
1463
+ The whole declaration is the kind - `baseURL` and `OPENROUTER_API_KEY` are its
1429
1464
  defaults, and built-in kinds are usable as provider names, so a `providers:`
1430
1465
  entry is only worth writing when it adds something:
1431
1466
 
@@ -1435,7 +1470,7 @@ agents:
1435
1470
  model: openrouter:anthropic/claude-sonnet-4.5
1436
1471
  ```
1437
1472
 
1438
- **Model ids** are `vendor/model` and may carry a variant suffix after a colon —
1473
+ **Model ids** are `vendor/model` and may carry a variant suffix after a colon -
1439
1474
  `:free`, `:nitro` (throughput-routed), `:floor` (price-routed), `:online` (web
1440
1475
  search). Both survive the shorthand, because only the _first_ colon separates:
1441
1476
 
@@ -1458,7 +1493,7 @@ the destination model understands.
1458
1493
  the upstream provider (OpenRouter's `provider` field, renamed because `provider:`
1459
1494
  already means the connection); `fallbacks` lists other _models_ to try when none
1460
1495
  can serve it (its `models` field). `allowFallbacks`, below, is a third thing
1461
- again — whether the gateway may look past `order`:
1496
+ again - whether the gateway may look past `order`:
1462
1497
 
1463
1498
  ```yaml
1464
1499
  models:
@@ -1478,9 +1513,9 @@ there is a load error.
1478
1513
 
1479
1514
  | Key | Value |
1480
1515
  | ------------------- | ---------------------------------------------------------------------------- |
1481
- | `order` | Providers to try first — a _preference_, not a restriction |
1516
+ | `order` | Providers to try first - a _preference_, not a restriction |
1482
1517
  | `only` / `ignore` | Restrict serving to, or away from, these |
1483
- | `allowFallbacks` | May the gateway go beyond `order` — **on** unless set `false` |
1518
+ | `allowFallbacks` | May the gateway go beyond `order` - **on** unless set `false` |
1484
1519
  | `sort` | `price`, `throughput`, `latency`, `exacto` |
1485
1520
  | `requireParameters` | Skip a provider that would drop a parameter rather than serve it |
1486
1521
  | `dataCollection` | `allow` \| `deny` |
@@ -1491,12 +1526,12 @@ there is a load error.
1491
1526
  sits alongside `routing`, not inside it.
1492
1527
 
1493
1528
  **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
1529
+ for the same reason `reasoningEffort` is - the gateway's list moves faster than a
1530
+ schema would - so nothing local rejects them, and since `allowFallbacks` defaults
1496
1531
  on, an unknown name is skipped and the request quietly succeeds somewhere else.
1497
1532
  What _is_ checked is checked by the API rather than at load: a bad `sort` or
1498
1533
  `quantizations` returns `400 provider.sort: Invalid input`. Use `only`, or
1499
- `allowFallbacks: false`, when the constraint is meant to bind — an unroutable
1534
+ `allowFallbacks: false`, when the constraint is meant to bind - an unroutable
1500
1535
  request is then a 404 instead of a silent reroute.
1501
1536
 
1502
1537
  **Attribution** goes in `headers:`; there is no dedicated field because that one
@@ -1527,11 +1562,11 @@ curl -s https://openrouter.ai/api/v1/models | jq -r '
1527
1562
  `supported_parameters` decides whether `tools`, `tool_choice` and
1528
1563
  `reasoning_effort` are honoured. An agent with tools needs `tools` in that list.
1529
1564
 
1530
- **Not modelled yet:** `transforms`, `usage.include`, and per-call cost — the
1565
+ **Not modelled yet:** `transforms`, `usage.include`, and per-call cost - the
1531
1566
  gateway reports a price on every response, but it is not surfaced in the token
1532
1567
  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
1568
+ have no yaml spelling either - `maxPrice`, `preferredMaxLatency`,
1569
+ `preferredMinThroughput`, `enforceDistillableText` - and `sort` takes the string
1535
1570
  form only, not the `{ by, partition }` object. `models:` entries are strict, so
1536
1571
  writing any of these is a load error rather than a key that is silently dropped.
1537
1572
 
@@ -1545,27 +1580,27 @@ in place. `timeoutMs` and `headers` behave normally.
1545
1580
 
1546
1581
  Ask two separate questions: does the model **reason**, and does it **say what it
1547
1582
  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
1583
+ vendor except Gemini - which is why a reasoning model can burn thousands of
1549
1584
  thinking tokens while the CLI shows no progress at all.
1550
1585
 
1551
- **OpenAI** — reasoning text only exists on the **responses** API, and only as a
1586
+ **OpenAI** - reasoning text only exists on the **responses** API, and only as a
1552
1587
  summary. `api: responses` is not optional here, and not only for visibility:
1553
1588
  chat completions is the default, and it **refuses `reasoningEffort` together with
1554
- function tools** — `400 Function tools with reasoning_effort are not supported
1589
+ function tools** - `400 Function tools with reasoning_effort are not supported
1555
1590
  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.
1591
+ holds tools - which is nearly all of them - must name the api.
1557
1592
 
1558
1593
  ```yaml
1559
1594
  models:
1560
1595
  default:
1561
1596
  provider: openai
1562
- api: responses # required — chat completions refuses tools + reasoning
1597
+ api: responses # required - chat completions refuses tools + reasoning
1563
1598
  model: gpt-5.4-nano
1564
1599
  reasoningEffort: medium # how hard it thinks
1565
1600
  reasoningSummary: auto # whether you get to see it
1566
1601
  ```
1567
1602
 
1568
- **Anthropic** — `thinkingBudgetTokens` turns extended thinking on, and it is spent
1603
+ **Anthropic** - `thinkingBudgetTokens` turns extended thinking on, and it is spent
1569
1604
  _out of_ `maxTokens`, so raise that too or the answer has no room left after the
1570
1605
  thinking. The runtime keeps 1024 tokens of headroom whatever you write, so a cap
1571
1606
  below the budget is corrected rather than rejected. Read §7.7 first: this
@@ -1580,7 +1615,7 @@ models:
1580
1615
  thinkingBudgetTokens: 8000
1581
1616
  ```
1582
1617
 
1583
- **Gemini** — thought summaries are on by default (`includeThoughts: true`); what
1618
+ **Gemini** - thought summaries are on by default (`includeThoughts: true`); what
1584
1619
  varies is the budget. Gemini 3 takes `thinkingLevel`, Gemini 2.5 takes
1585
1620
  `thinkingBudget` in tokens.
1586
1621
 
@@ -1597,19 +1632,19 @@ running tail above the answer, and the one-shot path prints it under `--live`.
1597
1632
  The full chain is kept in the trajectory either way and is in the inspect report,
1598
1633
  so turning summaries off costs visibility, not the audit trail.
1599
1634
 
1600
- The cost is real — a summary is extra output tokens on every call — so leave it
1635
+ The cost is real - a summary is extra output tokens on every call - so leave it
1601
1636
  on where someone is watching and reach for a cheaper tier before turning effort
1602
1637
  up (§7.8).
1603
1638
 
1604
1639
  ### 7.7 Known traps
1605
1640
 
1606
- - **A model id with no provider prefix** — `gemini-3.5-flash` resolves to the
1641
+ - **A model id with no provider prefix** - `gemini-3.5-flash` resolves to the
1607
1642
  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
1643
+ Write `google:gemini-3.5-flash` - §7.3.
1644
+ - **OpenAI reasoning without `api: responses`** - chat completions rejects
1610
1645
  `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
1646
+ reasoning summary even without tools - §7.6.
1647
+ - **Anthropic + `thinkingBudgetTokens` + multi-turn tool use** - thinking-block
1613
1648
  signatures are not replayed, and the API rejects the follow-up. Leave extended
1614
1649
  thinking off for tool-using agents.
1615
1650
  - **`reasoningEffort: minimal`** is rejected by the `gpt-5.4-*` family; use `low`.
@@ -1617,16 +1652,16 @@ up (§7.8).
1617
1652
  → the `project_id` inside the key file named by `GOOGLE_APPLICATION_CREDENTIALS`.
1618
1653
  gcloud user credentials and metadata-server credentials carry no project id, so
1619
1654
  those deployments must set the variable.
1620
- - **OpenRouter + a capability the route does not have** — a valid id and a valid
1655
+ - **OpenRouter + a capability the route does not have** - a valid id and a valid
1621
1656
  key still fail at the first request (`404 No endpoints found that support image
1622
1657
  input`, or tools quietly unused). This is not a config error and `zen check`
1623
1658
  cannot see it: check the catalog (§7.5). Cheap `:free` tiers are the usual
1624
- offenders — they are frequently text-only.
1659
+ offenders - they are frequently text-only.
1625
1660
  - **Swapping an OpenRouter id is not a like-for-like change.** Two models behind
1626
1661
  one gateway differ in modalities, tool support and reasoning; re-run the case
1627
1662
  that uses the capability, not just any case.
1628
1663
  - **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.
1664
+ publishes no embeddings API. Point it at another provider - §3.1.1.
1630
1665
  - **Changing `dimensions:` on an embedding invalidates every stored vector.**
1631
1666
  Widths are not comparable, so anything already indexed has to be re-embedded.
1632
1667
 
@@ -1634,7 +1669,7 @@ input`, or tools quietly unused). This is not a config error and `zen check`
1634
1669
 
1635
1670
  1. Start every agent on `balanced`.
1636
1671
  2. Demote to `router` any agent whose job is classification, extraction, or a
1637
- fixed handoff — measure, do not guess.
1672
+ fixed handoff - measure, do not guess.
1638
1673
  3. Promote to `careful` only after seeing a concrete failure that a stronger
1639
1674
  model actually fixes.
1640
1675
  4. Never fix an instruction-following bug with a model upgrade (§4.4).
@@ -1650,18 +1685,18 @@ There is no compiler for prose. Substitutes, in order of value:
1650
1685
  1. **`zen check`.** Most structural mistakes are load errors, and this is the
1651
1686
  fastest way to see all of them: it validates `agents.yaml`, checks that every
1652
1687
  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
1688
+ selectors resolve, and that the models have credentials - without stopping at
1654
1689
  the first problem and without calling anything. `zen check --json` if you are
1655
1690
  parsing it.
1656
1691
  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
1692
+ 3. **Read the inspect report** - it shows the assembled prompt, every request and
1658
1693
  response, tool calls, skill activations and cost. Behaviour questions are
1659
1694
  answered there, not by re-reading the YAML.
1660
1695
  4. **Re-run `zen check` after any rename.** It is what catches the handoff, the
1661
1696
  skill or the prompt path that a rename silently unlinked.
1662
1697
  5. **Watch the token accounting.** A change that doubles prefix size is a
1663
1698
  regression even if the answer improved.
1664
- 6. **After changing a model id, exercise the capability it was chosen for** —
1699
+ 6. **After changing a model id, exercise the capability it was chosen for** -
1665
1700
  send an image, force a tool call, ask for reasoning. `zen check` proves the
1666
1701
  credential resolves, not that the route serves images or honours `tools`; on a
1667
1702
  gateway that gap is a request-time 404 (§7.5).
@@ -1675,13 +1710,13 @@ first thing that looks like a credential problem:
1675
1710
  zen models test <ref> # one real call, one verdict
1676
1711
  ```
1677
1712
 
1678
- - **refused** — the credential was rejected. `zen key check <provider>`.
1679
- - **blocked** — the credential was _accepted_ and the account said no: an API
1713
+ - **refused** - the credential was rejected. `zen key check <provider>`.
1714
+ - **blocked** - the credential was _accepted_ and the account said no: an API
1680
1715
  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
1716
+ rotate the key** - a new one is refused identically. The fix is printed under
1682
1717
  the verdict; for a disabled Google API it is the exact `gcloud services
1683
1718
  enable …` line.
1684
- - **no answer** — the network. Try again.
1719
+ - **no answer** - the network. Try again.
1685
1720
 
1686
1721
  Either run the printed fix, or take a model that works and put the ref it prints
1687
1722
  into `agents.yaml`:
@@ -1722,19 +1757,19 @@ Before finishing any change here:
1722
1757
  - [ ] No facts, rates or figures embedded in a prompt
1723
1758
  - [ ] No hedging, no meta-talk about the runtime
1724
1759
 
1725
- **Paths (§2.5 — run its checklist over the three trees)**
1760
+ **Paths (§2.5 - run its checklist over the three trees)**
1726
1761
 
1727
1762
  - [ ] Every absolute path in `INSTRUCTIONS.md`, `agents/prompts/*.md` and
1728
1763
  `agents/skills/*/SKILL.md` is under `/workspace`, `/assets`, `/skills` or
1729
1764
  `/memory`
1730
- - [ ] No prompt or skill names a project file — `agents.yaml`, a prompt path, a
1765
+ - [ ] No prompt or skill names a project file - `agents.yaml`, a prompt path, a
1731
1766
  skill's repository path, `sandbox/Dockerfile`, `.env`, `sessions/`
1732
1767
  - [ ] No `../`, `~`, or host path; nothing assumes the workspace is the project
1733
1768
  - [ ] Every `/assets/…` and `/skills/<name>/…` path names a committed file
1734
1769
 
1735
1770
  **Skills**
1736
1771
 
1737
- - [ ] Every skill is `<name>/SKILL.md` — no bare `<name>.md` in the catalog
1772
+ - [ ] Every skill is `<name>/SKILL.md` - no bare `<name>.md` in the catalog
1738
1773
  - [ ] Every skill has a `description` that says _when it is needed_
1739
1774
  - [ ] `preload` is reserved for content the model would never decline
1740
1775
  - [ ] `preload` entries also appear in `allow` where `allow` is used
@@ -1746,11 +1781,11 @@ Before finishing any change here:
1746
1781
 
1747
1782
  - [ ] Every agent holds the narrowest set its job needs
1748
1783
  - [ ] An agent that only reads is not holding `write_file`, `apply_patch`,
1749
- `move_file` or `delete_file` — subtract them from `workspace:*`
1784
+ `move_file` or `delete_file` - subtract them from `workspace:*`
1750
1785
  - [ ] `sandbox:*` is granted only where a shell is actually needed
1751
1786
  - [ ] `sandbox.persist: true`, unless a throwaway rootfs is wanted on purpose
1752
1787
  - [ ] 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
1788
+ installing it every run - add it to `sandbox/Dockerfile` if `build:` is used
1754
1789
  - [ ] `sandbox.env` lists names only, and nothing credential-shaped
1755
1790
  - [ ] `exa:*` is granted only where the live web is actually needed, and the
1756
1791
  prompt says when to trust it over what the model already believes
@@ -1769,7 +1804,7 @@ Before finishing any change here:
1769
1804
 
1770
1805
  **Secrets**
1771
1806
 
1772
- - [ ] No key literal in YAML, prompt, skill or log — `${VAR}` only
1807
+ - [ ] No key literal in YAML, prompt, skill or log - `${VAR}` only
1773
1808
  - [ ] `.env` is git-ignored
1774
1809
 
1775
1810
  ---
@@ -1783,30 +1818,30 @@ Before finishing any change here:
1783
1818
  | Ignores a rule that only applies sometimes | Move the rule into a skill with a sharp description |
1784
1819
  | Never loads the skill it should | The skill's `description`; or `preload` it |
1785
1820
  | 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 |
1821
+ | Invents a number | A skill holding the figure, or a script that computes it - §3.4.1 |
1822
+ | A skill in the catalog is never offered | It is a bare `<name>.md`; move it to `<name>/SKILL.md` - §3.4 |
1823
+ | Cannot find a file its own skill names | Absolute `/skills/<name>/…` path, and `sandbox:*` - §3.4.1 |
1824
+ | Tries to open `agents.yaml` or its own prompt | The prompt names a file no agent can see - §2.5 |
1825
+ | Says a file is missing that is in the project | The workspace is not the project directory - §2.5 |
1826
+ | Rewrites a whole file to change one line | A prompt line preferring `apply_patch` - §3.6 |
1792
1827
  | Edits files it should only be reading | Subtract the mutating tools, or `zen run --read-only` |
1793
1828
  | 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 |
1829
+ | Installs the same packages on every run | Put them in `sandbox/Dockerfile`, or `sandbox.persist: true` - §3.7 |
1830
+ | Answers from stale knowledge of the world | Grant `web_search` + `web_read`, and say when - §3.8 |
1831
+ | Cites a page it only saw the excerpt of | A prompt line: `web_read` before quoting - §3.8 |
1832
+ | Every web call refuses | No Exa key: `zen key add exa` - `zen check` warns - §3.8 |
1833
+ | A model or embedder refuses every call | `zen models test <ref>` - if `blocked`, `zen models pick` - §8 |
1799
1834
  | Answers instead of routing | Router prompt prohibition; check `handoffs:` |
1800
1835
  | 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 |
1836
+ | Gets stuck in the agent it routed to | The target needs a handoff back - §6.3 |
1802
1837
  | 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 |
1838
+ | Works through N independent items serially | `fork:` on that agent, and a prompt line - §6.4 |
1804
1839
  | Forks when the steps actually depend | Prompt line: branches cannot see each other |
1805
1840
  | Slow and expensive on trivial cases | Demote that agent's model tier / reasoning effort |
1806
1841
  | 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 |
1842
+ | Shows no reasoning while it works | Turn summaries on for that model - §7.6 |
1808
1843
  | 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 |
1844
+ | Breaks at load with a named path | Read the message - it names the exact key |
1810
1845
 
1811
1846
  ---
1812
1847
 
@@ -1818,28 +1853,28 @@ Before finishing any change here:
1818
1853
  - **Facts in prompts.** A fee schedule inside `INSTRUCTIONS.md`. It cannot be
1819
1854
  versioned, cannot be shared, and is paid for on every call.
1820
1855
  - **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.
1856
+ never grow the table or script the next revision of the rule will want - §3.4.
1822
1857
  - **Pointing an agent at the project.** A prompt or skill naming `agents.yaml`,
1823
1858
  `INSTRUCTIONS.md`, `agents/skills/<name>/…` or `sandbox/Dockerfile`. None of
1824
1859
  them are on any path the agent can open, so the instruction fails on the turn
1825
- that follows it — §2.5.
1860
+ that follows it - §2.5.
1826
1861
  - **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.
1862
+ will get wrong, in a folder that could have held the script - §3.4.1.
1828
1863
  - **Politeness padding.** "Please try your best to be helpful." Costs tokens,
1829
1864
  changes nothing.
1830
1865
  - **Commented implementation notes.** `agents.yaml` explaining how skill
1831
- discovery or prompt caching works. That is this file's job — §3.1.
1866
+ discovery or prompt caching works. That is this file's job - §3.1.
1832
1867
  - **Fixing prompts with models.** See §4.4.
1833
1868
  - **The chatty router.** A router that answers before handing off, because its
1834
1869
  prompt never forbade it.
1835
1870
  - **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.
1871
+ once and then never sees the conversation again - §6.3.
1837
1872
  - **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.
1873
+ conversation on it. That is a `fork:`, which returns by itself - §6.4.
1839
1874
  - **Forking a chain.** Branches never see each other, so a fork whose second
1840
1875
  branch needs the first branch's answer is a sequence wearing a fork's clothes.
1841
1876
  - **Installing the toolchain every run.** A prompt that begins with `apt-get
1842
- install` is an `image:` that was never set — §3.7.
1877
+ install` is an `image:` that was never set - §3.7.
1843
1878
  - **Volatile prefix.** "Current date: …" in `INSTRUCTIONS.md`. Permanent cache
1844
1879
  miss.
1845
1880
 
@@ -1849,16 +1884,16 @@ install` is an `image:` that was never set — §3.7.
1849
1884
 
1850
1885
  This file is loaded on every request in this repository, so it is subject to its
1851
1886
  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
1887
+ is true of every task - if it is not, it is a skill under `.github/skills/`, or
1853
1888
  a prompt file under `.github/prompts/` if it is a procedure (§2.4).
1854
1889
 
1855
1890
  Update it when:
1856
1891
 
1857
1892
  - 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
1893
+ - a model alias is added, retired or repointed - §7.2 must match `agents.yaml`
1894
+ - a vendor trap is discovered - add it to §7.7
1895
+ - a recurring review comment appears twice - turn it into a checklist line in §9
1896
+ - a runtime capability is added - describe it here, or it will not be used
1862
1897
 
1863
1898
  Do not let it grow without pruning. When a section is only true of one agent, it
1864
1899
  belongs in that agent's prompt, not here.