@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.
- package/README.md +563 -206
- package/dist/banner.d.ts +5 -1
- package/dist/banner.js +2 -2
- package/dist/command.d.ts +5 -0
- package/dist/commands/init.js +46 -2
- package/dist/main.js +10 -6
- package/dist/validate.js +1 -1
- package/package.json +2 -2
- package/templates/editor/.github/copilot-instructions.md +253 -218
- package/templates/editor/.github/prompts/new-agent.prompt.md +6 -6
- package/templates/editor/.github/prompts/new-skill.prompt.md +7 -7
- package/templates/editor/.github/prompts/review-project.prompt.md +1 -1
- package/templates/editor/.github/prompts/sync-with-spec.prompt.md +26 -26
- package/templates/editor/.github/skills/zen-cli/SKILL.md +62 -6
- package/templates/editor/.github/skills/zen-cli/references/check.md +3 -3
- package/templates/editor/.github/skills/zen-cli/references/faker.md +14 -14
- package/templates/editor/.github/skills/zen-cli/references/frame.md +6 -6
- package/templates/editor/.github/skills/zen-cli/references/inspect.md +3 -3
- package/templates/editor/.github/skills/zen-cli/references/keys.md +9 -9
- package/templates/editor/.github/skills/zen-cli/references/memory.md +11 -11
- package/templates/editor/.github/skills/zen-cli/references/models.md +8 -8
- package/templates/editor/.github/skills/zen-cli/references/projects.md +3 -3
- package/templates/editor/.github/skills/zen-cli/references/rag.md +191 -56
- package/templates/editor/.github/skills/zen-cli/references/run.md +3 -3
- package/templates/editor/.github/skills/zen-cli/references/sandbox.md +8 -8
- package/templates/editor/.github/skills/zen-memory/SKILL.md +50 -50
- package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +71 -48
- package/templates/editor/.github/skills/zen-rag-schema/SKILL.md +67 -67
- package/templates/editor/.vscode/settings.json +5 -2
- package/templates/project/INSTRUCTIONS.md +1 -1
- package/templates/project/assets/README.md +1 -1
- package/templates/project/sandbox/Dockerfile.tmpl +3 -0
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
# Copilot instructions
|
|
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
|
|
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`
|
|
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
|
|
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
|
|
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**
|
|
53
|
-
|
|
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
|
|
97
|
-
activation
|
|
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")
|
|
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
|
|
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
|
|
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
|
|
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
|
|
145
|
-
├── sandbox/Dockerfile the image commands run in
|
|
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**
|
|
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**
|
|
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**
|
|
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**
|
|
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`)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
236
|
-
| `/assets` | `assets/`, reference material
|
|
237
|
-
| `/skills` | the whole skill catalog
|
|
238
|
-
| `/memory` | files carried by remembered nodes
|
|
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
|
|
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
|
|
297
|
+
prose**, in the prompt - never sent to read the file that configured it.
|
|
263
298
|
|
|
264
|
-
#### Checklist
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
345
|
-
| `fork` | `true`, or `{ agents, maxBranches }`
|
|
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**
|
|
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**
|
|
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
|
|
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**
|
|
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**
|
|
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**
|
|
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`**
|
|
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**
|
|
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
|
|
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
|
|
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
|
|
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**
|
|
439
|
-
| `maxBatch` | **gemini only**
|
|
440
|
-
| `routing` | **openrouter only**
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
553
|
-
needs
|
|
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
|
|
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**
|
|
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**
|
|
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
|
|
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
|
|
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
|
|
660
|
-
(§3.8)
|
|
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
|
|
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
|
|
691
|
-
checked
|
|
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/...`
|
|
695
|
-
directory has inside the sandbox (§3.7)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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**
|
|
790
|
-
repository
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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:`
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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:`
|
|
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
|
|
932
|
-
of text
|
|
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
|
|
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
|
|
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`
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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`**
|
|
1131
|
+
- **`index`** - names and descriptions rendered into the system prompt. The
|
|
1097
1132
|
default and right answer for most catalogs.
|
|
1098
|
-
- **`search`**
|
|
1133
|
+
- **`search`** - no index, only a `skill_search` tool. For catalogs too large to
|
|
1099
1134
|
render.
|
|
1100
|
-
- **`none`**
|
|
1101
|
-
- **`preload`**
|
|
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`**
|
|
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**
|
|
1112
|
-
another
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1173
|
-
- It feels tidier
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1200
|
-
lenses, six candidate suppliers
|
|
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."_
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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**
|
|
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
|
|
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**
|
|
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
|
|
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`
|
|
1293
|
-
- `compact`
|
|
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`
|
|
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
|
|
1300
|
-
than one region, fork one branch per region and merge their findings"_
|
|
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
|
|
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**
|
|
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
|
|
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
|
|
1402
|
-
| `reasoningSummary` | openai, openrouter | `auto` \| `concise` \| `detailed`. On openai **needs `api: responses`**
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
1495
|
-
schema would
|
|
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
|
|
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
|
|
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
|
|
1534
|
-
`preferredMinThroughput`, `enforceDistillableText`
|
|
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
|
|
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**
|
|
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**
|
|
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
|
|
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
|
|
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**
|
|
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**
|
|
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
|
|
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**
|
|
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`
|
|
1609
|
-
- **OpenAI reasoning without `api: responses`**
|
|
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
|
|
1612
|
-
- **Anthropic + `thinkingBudgetTokens` + multi-turn tool use**
|
|
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**
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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**
|
|
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**
|
|
1679
|
-
- **blocked**
|
|
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**
|
|
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**
|
|
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
|
|
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
|
|
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`
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
1787
|
-
| A skill in the catalog is never offered | It is a bare `<name>.md`; move it to `<name>/SKILL.md`
|
|
1788
|
-
| Cannot find a file its own skill names | Absolute `/skills/<name>/…` path, and `sandbox:*`
|
|
1789
|
-
| Tries to open `agents.yaml` or its own prompt | The prompt names a file no agent can see
|
|
1790
|
-
| Says a file is missing that is in the project | The workspace is not the project directory
|
|
1791
|
-
| Rewrites a whole file to change one line | A prompt line preferring `apply_patch`
|
|
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`
|
|
1795
|
-
| Answers from stale knowledge of the world | Grant `web_search` + `web_read`, and say when
|
|
1796
|
-
| Cites a page it only saw the excerpt of | A prompt line: `web_read` before quoting
|
|
1797
|
-
| Every web call refuses | No Exa key: `zen key add exa`
|
|
1798
|
-
| A model or embedder refuses every call | `zen models test <ref>`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1859
|
-
- a vendor trap is discovered
|
|
1860
|
-
- a recurring review comment appears twice
|
|
1861
|
-
- a runtime capability is added
|
|
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.
|