@zenera/cli 1.1.15 → 1.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +201 -129
  2. package/dist/banner.d.ts +5 -1
  3. package/dist/banner.js +2 -2
  4. package/dist/catalog.js +19 -20
  5. package/dist/command.d.ts +5 -0
  6. package/dist/commands/check.js +5 -1
  7. package/dist/commands/init.js +41 -6
  8. package/dist/commands/run.js +4 -0
  9. package/dist/engine.js +6 -0
  10. package/dist/env.d.ts +20 -0
  11. package/dist/env.js +65 -0
  12. package/dist/keys.d.ts +2 -0
  13. package/dist/keys.js +2 -0
  14. package/dist/main.js +10 -6
  15. package/dist/sandbox.d.ts +9 -0
  16. package/dist/sandbox.js +23 -3
  17. package/dist/scaffold.d.ts +6 -0
  18. package/dist/scaffold.js +56 -15
  19. package/dist/validate.js +57 -15
  20. package/package.json +2 -2
  21. package/templates/editor/.github/copilot-instructions.md +501 -301
  22. package/templates/editor/.github/prompts/new-agent.prompt.md +6 -6
  23. package/templates/editor/.github/prompts/new-skill.prompt.md +7 -7
  24. package/templates/editor/.github/prompts/review-project.prompt.md +54 -15
  25. package/templates/editor/.github/prompts/sync-with-spec.prompt.md +229 -42
  26. package/templates/editor/.github/prompts/update-spec-from-feedback.prompt.md +142 -0
  27. package/templates/editor/.github/skills/zen-cli/SKILL.md +62 -6
  28. package/templates/editor/.github/skills/zen-cli/references/check.md +9 -6
  29. package/templates/editor/.github/skills/zen-cli/references/faker.md +14 -14
  30. package/templates/editor/.github/skills/zen-cli/references/frame.md +11 -9
  31. package/templates/editor/.github/skills/zen-cli/references/inspect.md +3 -3
  32. package/templates/editor/.github/skills/zen-cli/references/keys.md +9 -9
  33. package/templates/editor/.github/skills/zen-cli/references/memory.md +11 -11
  34. package/templates/editor/.github/skills/zen-cli/references/models.md +8 -8
  35. package/templates/editor/.github/skills/zen-cli/references/projects.md +23 -6
  36. package/templates/editor/.github/skills/zen-cli/references/rag.md +191 -56
  37. package/templates/editor/.github/skills/zen-cli/references/run.md +3 -3
  38. package/templates/editor/.github/skills/zen-cli/references/sandbox.md +14 -8
  39. package/templates/editor/.github/skills/zen-memory/SKILL.md +144 -72
  40. package/templates/editor/.github/skills/zen-memory/scripts/check-instructions.sh +115 -0
  41. package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +71 -48
  42. package/templates/editor/.github/skills/zen-rag-schema/SKILL.md +67 -67
  43. package/templates/editor/.github/skills/zen-review/SKILL.md +123 -0
  44. package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +119 -0
  45. package/templates/editor/.github/skills/zen-review/scripts/review.sh +104 -0
  46. package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +197 -0
  47. package/templates/editor/.github/skills/zen-spec-sync/scripts/snapshot.sh +200 -0
  48. package/templates/editor/.vscode/settings.json +46 -3
  49. package/templates/parts/memory-terms.yaml.tmpl +16 -0
  50. package/templates/parts/memory.yaml.tmpl +12 -0
  51. package/templates/project/SPECIFICATION.md.tmpl +7 -0
  52. package/templates/project/{INSTRUCTIONS.md → agents/instructions.md} +1 -1
  53. package/templates/project/agents/memory-instructions.md +278 -0
  54. package/templates/project/agents.yaml.tmpl +9 -0
  55. package/templates/project/assets/README.md +1 -1
  56. package/templates/project/env +22 -0
  57. package/templates/project/sandbox/Dockerfile.tmpl +3 -0
@@ -1,17 +1,17 @@
1
1
  ---
2
2
  name: zen-memory
3
- description: How agent memory is organised and how to configure it in `agents.yaml` — the `memory:` block, per-agent `access`/`sees`/`writes`/`autoRecall`, the four `memory_*` tools, kinds and relations, how recall ranks and stitches a subgraph, and how to design a memory strategy for a project. Includes what to commit and what never to, how to keep a working file under `/memory` and re-run it later, and how to put memory in front of a `zen rag schema` or `zen rag docs` index so a search that already succeeded once is not paid for again.
3
+ description: How agent memory is organised and how to configure it in `agents.yaml` - the `memory:` block, per-agent `access`/`sees`/`writes`/`autoRecall`, the four `memory_*` tools, kinds and relations, how recall ranks and stitches a subgraph, and how to design a memory strategy for a project. Includes the usage rules every memory-enabled project must carry in `agents/memory-instructions.md` (references/memory-instructions.md) and the script that says whether that copy is still current (scripts/check-instructions.sh), what to commit and what never to, how to keep a working file under `/memory` and re-run it later, and how to put memory in front of a `zen rag schema` or `zen rag docs` index so a search that already succeeded once is not paid for again.
4
4
  ---
5
5
 
6
6
  # Memory
7
7
 
8
8
  Memory is what an agent knows that nobody wrote down for it. A skill is
9
9
  authored, reviewed and versioned; a memory is **produced by a run** and read by
10
- the runs that follow. Both are knowledge, and they are not interchangeable —
10
+ the runs that follow. Both are knowledge, and they are not interchangeable -
11
11
  anything authoritative belongs in a skill, where a human owns it.
12
12
 
13
- It is a **graph**, not a list of rows. A node is one thing worth keeping — a
14
- request, a plan, a fact, a script, a call that was made — and an edge says how
13
+ It is a **graph**, not a list of rows. A node is one thing worth keeping - a
14
+ request, a plan, a fact, a script, a call that was made - and an edge says how
15
15
  one led to another. That shape is the point: a script an agent wrote is useless
16
16
  without the request that asked for it and the endpoint it calls, and a store
17
17
  that can only return a ranked list leaves the joining to the model, every time.
@@ -36,11 +36,11 @@ agents:
36
36
  That is the whole feature working the obvious way: the agent can search, load
37
37
  and commit, and it recalls automatically before answering new user input.
38
38
 
39
- The top-level block is optional — an agent that says `memory: true` gets the
39
+ The top-level block is optional - an agent that says `memory: true` gets the
40
40
  defaults without one. A project that mentions memory nowhere opens no store and
41
41
  creates no directory.
42
42
 
43
- ### `memory:` — the graph itself
43
+ ### `memory:` - the graph itself
44
44
 
45
45
  | Field | Type | Default | Meaning |
46
46
  | ----------- | ------------- | --------------- | ----------------------------------- |
@@ -53,7 +53,7 @@ creates no directory.
53
53
  memory phrased the way the query happened to be phrased and misses the rest,
54
54
  which is the failure mode that makes people conclude memory does not work. The
55
55
  width is learned from the first response rather than declared, and changing the
56
- model afterwards is **refused** rather than silently mixing two vector spaces —
56
+ model afterwards is **refused** rather than silently mixing two vector spaces -
57
57
  so choose it before the graph has anything in it.
58
58
 
59
59
  ```yaml
@@ -63,11 +63,11 @@ memory:
63
63
  embedding: main
64
64
  ```
65
65
 
66
- `zen memory stats` says `embedding none — recall falls back to term overlap`
66
+ `zen memory stats` says `embedding none - recall falls back to term overlap`
67
67
  when this was skipped, and a vector count below the node count means some nodes
68
68
  were committed before an embedder existed.
69
69
 
70
- ### `agents[].memory` — who may see and write what
70
+ ### `agents[].memory` - who may see and write what
71
71
 
72
72
  ```yaml
73
73
  agents:
@@ -104,7 +104,7 @@ which of them the model is offered:
104
104
  | --------------- | -------------------------------------------------------------------------- |
105
105
  | `memory_search` | Ranks, then stitches. Returns an outline of a subgraph and a legend of ids |
106
106
  | `memory_load` | Reads whole nodes by id. **The only call that counts as use** |
107
- | `memory_commit` | Writes a subgraph — nodes and the edges between them — in one transaction |
107
+ | `memory_commit` | Writes a subgraph - nodes and the edges between them - in one transaction |
108
108
  | `memory_forget` | Removes nodes, their vectors and their file bytes together |
109
109
 
110
110
  Search and load are split on purpose. Search hands back context the model never
@@ -112,7 +112,7 @@ asked for; counting that as use would poison recency ranking. Commit is one
112
112
  call because a remembered thing is a subgraph, and building it with three calls
113
113
  leaves the graph half-formed when the model stops early.
114
114
 
115
- The system prompt already explains all of this to the agent — how to read a
115
+ The system prompt already explains all of this to the agent - how to read a
116
116
  recollection, when committing is worthwhile, why correction is a new node. That
117
117
  text is **derived from `access`**, so an agent that cannot write is never told
118
118
  how to. Do not restate it in a prompt; state project-specific policy instead.
@@ -132,15 +132,15 @@ later.
132
132
  | `fact` | a durable truth about the environment or the domain |
133
133
  | `snippet` | a short piece of code or configuration, too small to be a file |
134
134
  | `file` | an artifact kept whole under `/memory` and re-runnable |
135
- | `operation` | an external call that was made — an endpoint, command or query |
135
+ | `operation` | an external call that was made - an endpoint, command or query |
136
136
  | `preference` | a standing instruction from the user that applies to every run |
137
137
 
138
138
  | Relation | Reads as | Effect on recall |
139
139
  | ------------ | --------------------------------------------------------- | ------------------------------- |
140
- | `PRODUCED` | the source led to the target being made (task → artifact) | spine — pulled in first |
140
+ | `PRODUCED` | the source led to the target being made (task → artifact) | spine - pulled in first |
141
141
  | `CALLS` | the source invokes the target | spine |
142
142
  | `SUPERSEDES` | the source replaces the target; the target is stale | spine, and **hides** the target |
143
- | `INFORMED` | the source was context the target was built from | background — dropped first |
143
+ | `INFORMED` | the source was context the target was built from | background - dropped first |
144
144
 
145
145
  Override the vocabularies only if the defaults genuinely do not fit the domain.
146
146
  A wider vocabulary is a vaguer one, and a kind nothing else understands is a
@@ -156,7 +156,7 @@ that kind to build the system prompt.
156
156
  | `kind` | filters a search, and decides how it renders |
157
157
  | `audience` | who may see it. `['*']` is everyone; denied by default |
158
158
  | `createdAt` | provenance, and what `newer_than` filters on |
159
- | `lastUsedAt`, `useCount` | bumped by `memory_load` alone — the input to recency |
159
+ | `lastUsedAt`, `useCount` | bumped by `memory_load` alone - the input to recency |
160
160
  | `revision` | optimistic concurrency, via `expected_revision` on a commit |
161
161
  | `file` | the remembered bytes, if any |
162
162
 
@@ -167,7 +167,7 @@ that kind to build the system prompt.
167
167
  nodes are excluded, and so are `preference` nodes unless asked for by kind.
168
168
  2. **Order by recency, but do not admit by it.** Relevance alone clears the
169
169
  threshold; decay by `lastUsedAt` only breaks the tie afterwards. Memory that
170
- keeps proving useful surfaces ahead of memory written once and never read —
170
+ keeps proving useful surfaces ahead of memory written once and never read -
171
171
  without anything ageing out of reach however well it matches.
172
172
  3. **Stitch.** Breadth-first from every seed at once, ignoring edge direction,
173
173
  because a memory is as often reached from the artifact back to the request as
@@ -182,14 +182,14 @@ Defaults: 5 seeds, 2 hops, 25 nodes. `memory_search` takes `limit`, `max_hops`,
182
182
  What comes back is a `<memory-recollection>` block: an indented outline where
183
183
  the indentation _is_ the graph, each line reading `score kind id` or
184
184
  `→relation kind id`, with each node's text under it **clipped to 160
185
- characters**. That clip is a design constraint on how you write a node — see
185
+ characters**. That clip is a design constraint on how you write a node - see
186
186
  below.
187
187
 
188
188
  ### On disk
189
189
 
190
190
  ```
191
191
  <project>/memory/
192
- ├── manifest.json written LAST — a half-written memory reads as empty
192
+ ├── manifest.json written LAST - a half-written memory reads as empty
193
193
  ├── graph.json nodes and edges, parsed whole on open
194
194
  ├── vectors.f32 raw little-endian float32, a quarter the size of JSON
195
195
  ├── vectors.json the ids those vectors belong to
@@ -206,9 +206,9 @@ mounted **read-only** at `/memory` in the agent's namespace.
206
206
 
207
207
  **Would the next run otherwise have to work this out again?** That is the whole
208
208
  question. A script that ran, an interface that behaved differently from its
209
- documentation, a call sequence that turned out to be necessary — yes. The
209
+ documentation, a call sequence that turned out to be necessary - yes. The
210
210
  request restated, progress notes, anything unverified, anything a search already
211
- returned — no.
211
+ returned - no.
212
212
 
213
213
  The second test is **would it still be true next month**. Memory has no expiry
214
214
  and no reviewer. A fact about the environment survives; a fact about today's
@@ -222,9 +222,9 @@ ask for it, not the way the system that produced it names it.
222
222
 
223
223
  | Instead of | Write |
224
224
  | --------------------------------- | ------------------------------------------------------------------------------- |
225
- | `POST /v2/subs/{id}:cancel — 204` | `Cancelling a subscription: POST /v2/subs/{id}:cancel, 204, no body` |
226
- | `fix for the 409` | `A duplicate customer email returns 409, not 422 — retry is wrong` |
227
- | `script.py` | `Weekly invoice reconciliation script — reads billing CSV, writes a diff table` |
225
+ | `POST /v2/subs/{id}:cancel - 204` | `Cancelling a subscription: POST /v2/subs/{id}:cancel, 204, no body` |
226
+ | `fix for the 409` | `A duplicate customer email returns 409, not 422 - retry is wrong` |
227
+ | `script.py` | `Weekly invoice reconciliation script - reads billing CSV, writes a diff table` |
228
228
 
229
229
  Front-load the identifying facts: the first 160 characters are what appears in
230
230
  a recollection, and everything after that costs a `memory_load` to see.
@@ -238,7 +238,7 @@ task "reconcile invoices against the ledger for a month"
238
238
  PRODUCED → file the script that did it
239
239
  INFORMED ← fact "ledger exports use minor units; invoices use decimals"
240
240
  file
241
- CALLS → operation "GET /v2/invoices?created[gte]= — cursor paging"
241
+ CALLS → operation "GET /v2/invoices?created[gte]= - cursor paging"
242
242
  ```
243
243
 
244
244
  Committed separately, a later run finds a script with no idea why it exists, or
@@ -247,7 +247,7 @@ brings the rest.
247
247
 
248
248
  **Never edit a memory to correct it.** Commit the new one and link it to the old
249
249
  with `SUPERSEDES`. Recall follows the edge forward and hides what it points at,
250
- so the wrong answer stops being served without being destroyed — and the reason
250
+ so the wrong answer stops being served without being destroyed - and the reason
251
251
  a thing changed is often the useful part. `zen memory ls --stale` is the list of
252
252
  corrections that have happened.
253
253
 
@@ -255,7 +255,7 @@ A new node whose text is a near-duplicate (cosine ≥ 0.97) of one already there
255
255
  is **folded into it**, and the existing id comes back under the model's ref, so
256
256
  the links still land on the memory that was already present.
257
257
 
258
- ### Audiences: one graph, masked — not one graph each
258
+ ### Audiences: one graph, masked - not one graph each
259
259
 
260
260
  Every node carries an `audience`; `sees` is the set an agent reads. `*` is the
261
261
  public slice and is always included, so a binding can only ever **widen** what
@@ -275,7 +275,7 @@ agent wrote this" but "which agent would be misled by this":
275
275
  | One curator may delete | `access: full` on exactly one agent |
276
276
 
277
277
  When `writes` names exactly one label, the tool schema omits the field and
278
- applies it — there is nothing to choose and nothing for the model to get wrong.
278
+ applies it - there is nothing to choose and nothing for the model to get wrong.
279
279
  Neither `sees` nor `writes` is ever a tool parameter: an agent that could name
280
280
  its own audience could read another agent's slice just by asking.
281
281
 
@@ -285,7 +285,7 @@ model that can delete will occasionally tidy.
285
285
  ### Auto-recall
286
286
 
287
287
  On by default, because an agent that has to remember to go looking mostly does
288
- not. It fires **after new user input and after a hand-off** — not before every
288
+ not. It fires **after new user input and after a hand-off** - not before every
289
289
  call, because recalling on every turn costs tokens and defeats prompt caching
290
290
  for a marginal gain. A turn following a tool result does not re-recall.
291
291
 
@@ -294,7 +294,7 @@ for a marginal gain. A turn following a tool result does not re-recall.
294
294
  | `true` (default) | Almost everything |
295
295
  | `{ limit: 2 }` | A cheap model, or a graph big enough that five seeds is a page |
296
296
  | `{ limit: 8 }` | A research agent where breadth beats prompt size |
297
- | `false` | An agent that should decide for itself when to look — it keeps the tools |
297
+ | `false` | An agent that should decide for itself when to look - it keeps the tools |
298
298
 
299
299
  `autoRecall: false` is right for a tool-driven agent whose first turn is
300
300
  mechanical, and for anything where the user's message is a payload rather than a
@@ -302,7 +302,7 @@ question.
302
302
 
303
303
  ### Preferences
304
304
 
305
- A `preference` node is a standing instruction — "always report findings as a
305
+ A `preference` node is a standing instruction - "always report findings as a
306
306
  table", "use ISO dates". These are **not recalled by similarity**, because they
307
307
  are not about the request in front of the agent: an instruction on how to report
308
308
  is nowhere near a question about gateway rules in embedding space, and a search
@@ -319,7 +319,7 @@ their ids:
319
319
 
320
320
  They land in the cached prefix, so they cost once per run rather than once per
321
321
  turn, and the id is what lets a model replace one with `SUPERSEDES` instead of
322
- quietly ignoring it. They are left out of ordinary recall on purpose — they are
322
+ quietly ignoring it. They are left out of ordinary recall on purpose - they are
323
323
  in the prompt already.
324
324
 
325
325
  Commit one only when the user's own words generalise: "always", "from now on",
@@ -329,7 +329,7 @@ wrote.
329
329
 
330
330
  ## Remembering files, and running them again
331
331
 
332
- The workspace is disposable — it may be a container about to be discarded — so a
332
+ The workspace is disposable - it may be a container about to be discarded - so a
333
333
  reference to a path in it dangles the moment the run ends. `memory_commit` takes
334
334
  a `file` argument on a node instead, and copies the **bytes** out:
335
335
 
@@ -339,7 +339,7 @@ a `file` argument on a node instead, and copies the **bytes** out:
339
339
  {
340
340
  "ref": "s",
341
341
  "kind": "file",
342
- "text": "Invoice reconciliation script — reads the billing CSV, writes a diff table",
342
+ "text": "Invoice reconciliation script - reads the billing CSV, writes a diff table",
343
343
  "file": "reconcile.py"
344
344
  },
345
345
  { "ref": "t", "kind": "task", "text": "reconcile invoices against the ledger for March" }
@@ -358,7 +358,7 @@ lands at `<project>/memory/files/<id>.<ext>` and appears to the agent as:
358
358
  | Rule | Value |
359
359
  | --------- | ---------------------------------------------------------------- |
360
360
  | Size cap | 2 MiB. Over it, keep the artifact and commit a `snippet` instead |
361
- | Inlined | under 32 KiB — `memory_load` returns the contents |
361
+ | Inlined | under 32 KiB - `memory_load` returns the contents |
362
362
  | Larger | `memory_load` returns the path; open or run it |
363
363
  | Mount | `/memory`, **read-only** |
364
364
  | Extension | preserved, because it decides how the file runs and renders |
@@ -379,11 +379,14 @@ node explaining what it is and the graph cannot acquire orphans. To change a
379
379
  remembered file: copy it into the workspace, edit it there, commit the new one
380
380
  and link it with `SUPERSEDES` to the old.
381
381
 
382
- | Commit as | When |
383
- | --------- | ------------------------------------------------------------------------- |
384
- | `file` | It is runnable, or it is the artifact itself: a script, a config, a query |
385
- | `snippet` | It is a few lines that only make sense read: a request body, a fragment |
386
- | `fact` | It is a sentence. A file containing one sentence is a fact in a costume |
382
+ The node text describes and locates; the file carries the content. Reach for a
383
+ node with no file only when the whole of what you know fits on one line.
384
+
385
+ | Commit as | When |
386
+ | --------- | --------------------------------------------------------------------------- |
387
+ | `file` | It is content - a script, a config, a query, a passage, an assembled answer |
388
+ | `snippet` | It is a few lines that only make sense read, and nothing will ever run them |
389
+ | `fact` | It is one line. A file containing one sentence is a fact in a costume |
387
390
 
388
391
  ## Memory in front of a `zen rag` index
389
392
 
@@ -400,19 +403,27 @@ it is entirely a matter of **what you commit**.
400
403
  | Freshness | as fresh as the last `index` | as stale as it was allowed to get |
401
404
  | Cost of a query | one embedding round trip (`search`), or nothing (`list`/`grep`) | already in the prompt |
402
405
 
403
- So: **never copy index content into memory.** A schema pasted into a node goes
404
- stale silently and the index already answers in milliseconds; a documentation
405
- passage quoted into a node outlives the release it was true for and will look
406
- exactly as authoritative when it is wrong.
406
+ So: **never copy content out of an index that still changes.** A schema pasted
407
+ into a node goes stale silently and the index already answers in milliseconds; a
408
+ documentation passage quoted into a node outlives the release it was true for
409
+ and will look exactly as authoritative when it is wrong.
410
+
411
+ A **pinned** index is the exception, and a real one. Docs indexed from a
412
+ released version or a versioned reference tree cannot change under the memory,
413
+ so a passage taken out of one stays true as long as the project stays on that
414
+ version. Keep it as a `file` node whose text names the documents and line ranges
415
+ and says which version it was. That is as well as the route, never instead of
416
+ it.
407
417
 
408
- What memory should hold is the **route** — the resolution, not the source:
418
+ What memory should hold first is the **route** - the resolution, not the source:
409
419
 
410
420
  | Commit | Kind | Because |
411
421
  | --------------------------------------------------------------------------------- | ------------------ | --------------------------------------------------------------------------------- |
412
422
  | The endpoint that turned out to be the answer, and its shape | `operation` | A `search_api` costs a round trip and returns five ranked guesses; this names one |
413
423
  | The sequence of calls that was necessary | `plan` | No ranking finds an ordering. It is not in the document |
414
- | Where the answer lives in the docs — document name, heading, line range | `fact` | `zen rag docs show` reads it back verbatim and current |
415
- | A convention the index cannot state — auth, casing, base path, the error envelope | `fact` | It is not in the graph, and it is wrong to re-derive it every run |
424
+ | Where the answer lives in the docs - document name, heading, line range | `fact` | `zen rag docs show` reads it back verbatim and current |
425
+ | The passage itself, when the index is pinned to a released version | `file` | It cannot change under you, and the pointer alone makes the next run re-read it |
426
+ | A convention the index cannot state - auth, casing, base path, the error envelope | `fact` | It is not in the graph, and it is wrong to re-derive it every run |
416
427
  | Where the index itself is | `fact` | `-d` or `$ZEN_SCHEMA_DB`. An agent cannot guess a path |
417
428
  | A working request body, or a script that drives the API | `snippet` / `file` | The thing you would otherwise write again |
418
429
  | That a search returned nothing, and `grep` confirmed absence | `fact` | Proving a negative twice is pure waste |
@@ -420,7 +431,7 @@ What memory should hold is the **route** — the resolution, not the source:
420
431
  ### Why it makes search cheaper
421
432
 
422
433
  Auto-recall fires **before the model's first tool call of the turn**. So the
423
- recollection arrives before the decision to search is made — and a recalled
434
+ recollection arrives before the decision to search is made - and a recalled
424
435
  `operation` node naming `POST /v2/subs/{id}:cancel` removes the search
425
436
  entirely, rather than making it faster. When the recollection is only close, it
426
437
  still narrows: the model reaches for `list_api --name` or `grep_api` instead of
@@ -436,7 +447,7 @@ and the answer second.
436
447
  An index is rebuilt when the source changes; memory is not. Two habits keep the
437
448
  two from drifting:
438
449
 
439
- 1. **Name the version in the node text** — "Billing API v2", "acme\_4.1.0 docs".
450
+ 1. **Name the version in the node text** - "Billing API v2", "acme\_4.1.0 docs".
440
451
  A recollection about a version the project no longer uses is then visibly
441
452
  about the wrong thing rather than invisibly wrong.
442
453
  2. **Supersede after a re-index.** When a route changes, commit the new
@@ -450,9 +461,13 @@ cheap and settles it.
450
461
 
451
462
  ### The project skill is where this is written down
452
463
 
453
- An index used by a project needs a skill in that project describing it — see
454
- `zen-rag-schema` and `zen-rag-docs`. Memory policy belongs in the same skill,
455
- because it is the same subject:
464
+ An index used by a project needs a skill in that project describing it - see
465
+ `zen-rag-schema` and `zen-rag-docs`. The memory policy that is **about the
466
+ index** belongs in that skill, because it is the same subject and it is read at
467
+ the moment the index is. Policy that holds whatever the agent is doing -
468
+ audiences, what must never be written down - goes in
469
+ `agents/memory-policy-instructions.md` instead, which every agent gets whether
470
+ it loads the skill or not.
456
471
 
457
472
  ```md
458
473
  - Recalled routes are a shortcut, not authority. Confirm with `list_api --name`
@@ -460,10 +475,61 @@ because it is the same subject:
460
475
  - After solving something the index made you work for, commit it: the
461
476
  `operation` you ended on, linked to the `task` that asked and any `fact` you
462
477
  had to discover. Phrase the text as the question, not as the operation id.
463
- - Never commit schema text or documentation passages. Commit the pointer —
464
- document, heading, line range — and read it back with `zen rag docs show`.
478
+ - Never commit schema text or documentation passages copied out of a document
479
+ that still changes. Commit the pointer - document, heading, line range -
480
+ alongside what you concluded from it, and read the passage back with
481
+ `zen rag docs show` to check the conclusion still holds.
482
+ - Our docs are indexed from the pinned 1.4 tree, so a passage out of them is
483
+ safe to keep whole: commit it as a `file` node headed with the documents and
484
+ line ranges, next to the pointer and the conclusion.
485
+ ```
486
+
487
+ ## The house rules
488
+
489
+ **Every project that enables memory carries `references/memory-instructions.md`
490
+ in its own `agents/memory-instructions.md`**, as part of turning memory on. That
491
+ file is how the agent is told to use the store: read and search it only through
492
+ the tools, never through `/memory` itself, and what a node must carry to still be
493
+ worth having.
494
+
495
+ `zen init` writes it, because the projects it scaffolds have memory on from the
496
+ first run. Two cases are left to check by hand: a project that turned memory on
497
+ afterwards, and one whose copy has drifted from the reference or was deleted.
498
+
499
+ Neither is checked by eye. The skill ships the check, and it is one command:
500
+
501
+ ```sh
502
+ .github/skills/zen-memory/scripts/check-instructions.sh # the verdict
503
+ .github/skills/zen-memory/scripts/check-instructions.sh diff # every differing line
504
+ .github/skills/zen-memory/scripts/check-instructions.sh fix # copy the reference over
465
505
  ```
466
506
 
507
+ It says whether this project turns memory on at all, exits non-zero on a copy
508
+ that is missing or stale, and prints the `cp` that repairs it. When the only
509
+ difference is trailing whitespace it says so - that is the usual drift, it is
510
+ invisible on screen, and it is the reason reading the two files side by side
511
+ never caught it.
512
+
513
+ It is a copy, not a transcription - this reference already sits in the project,
514
+ because `zen init` and `zen open` write the whole `.github/` tree, and they write
515
+ it from the same file the project's copy came from. What `fix` runs, from the
516
+ project root, is exactly:
517
+
518
+ ```sh
519
+ cp .github/skills/zen-memory/references/memory-instructions.md agents/memory-instructions.md
520
+ ```
521
+
522
+ Everything the project keeps under `agents/` named `<topic>-instructions.md` is
523
+ prepended to every agent, in filename order, so this belongs in its own document
524
+ rather than as another section of `agents/instructions.md` - it arrives with
525
+ memory and it leaves with it.
526
+
527
+ **Keep that file a pure copy.** Project-specific policy - audiences, what must
528
+ never be written down, what an agent commits at the end of a job - goes in
529
+ `agents/memory-policy-instructions.md`, which filename order puts directly after
530
+ it. Then keeping up with a changed reference is the same one command again,
531
+ rather than a hand-patch around prose that has to be preserved.
532
+
467
533
  ## Inspecting and repairing it
468
534
 
469
535
  ```
@@ -472,8 +538,8 @@ zen memory [stats|ls|show|export|forget] [args] [options]
472
538
 
473
539
  `zen memory export --open` is the one to reach for: one self-contained HTML
474
540
  page with every node, the graph as a diagram, and the remembered file's actual
475
- content in the detail pane. It reads **unmasked** — when the mask is what is
476
- wrong, the hidden part is exactly the part you need — and it contacts no model,
541
+ content in the detail pane. It reads **unmasked** - when the mask is what is
542
+ wrong, the hidden part is exactly the part you need - and it contacts no model,
477
543
  so inspection is free and offline.
478
544
 
479
545
  | Question | Command |
@@ -524,32 +590,38 @@ agents:
524
590
 
525
591
  ## Review checklist
526
592
 
593
+ - [ ] `.github/skills/zen-memory/scripts/check-instructions.sh` exits zero, so
594
+ `agents/memory-instructions.md` is the reference byte for byte - and
595
+ project policy lives in `agents/memory-policy-instructions.md`.
527
596
  - [ ] `memory.embedding` is set, and was set before the graph had content.
528
- - [ ] Every agent that should learn has a `memory:` binding — the top-level
597
+ - [ ] Every agent that should learn has a `memory:` binding - the top-level
529
598
  block alone enables nothing.
530
599
  - [ ] `access: full` appears at most once.
531
600
  - [ ] `sees`/`writes` exist because an agent would be **misled** by the other
532
601
  slice, not merely because it does not need it.
533
602
  - [ ] `autoRecall: false` on any agent whose user message is a payload.
534
- - [ ] Nothing authoritative is expected to live here — that is a skill's job.
603
+ - [ ] Nothing authoritative is expected to live here - that is a skill's job.
535
604
  - [ ] The project's index skill says to verify a recalled route.
536
605
  - [ ] `zen memory export --open` after a week of use, to see what it actually
537
606
  learned rather than what you hoped.
538
607
 
539
608
  ## When it goes wrong
540
609
 
541
- | Symptom | Cause |
542
- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
543
- | The `memory_*` tools are missing | No `memory:` on the agent. They come from the binding, never from `tools:` |
544
- | `zen memory` says the project has none | Neither a `memory:` block nor an agent binding — nothing is opened and no directory is made |
545
- | Recall finds a memory only when reworded | No embedder. `zen memory stats` says `embedding none` |
546
- | Recall finds nothing after changing the embedder | Refused rather than mixed — a manifest records the model. Re-embed or change it back |
547
- | Vectors fewer than nodes | Some nodes were committed with no embedder; they are only reachable by term overlap |
548
- | The graph fills with restated requests | The commit rule is not in a prompt or skill. State the "would a later run redo this" test |
549
- | A wrong memory keeps coming back | It was edited instead of superseded, or superseded in the wrong direction — the **new** node is the source |
550
- | An agent cannot see a node you can | Its `audience` is not in that agent's `sees`. Invisible and missing are the same thing, on purpose |
551
- | "this agent cannot remember files" | The agent has no workspace, so a path cannot be resolved. Commit without `file` |
552
- | A remembered file will not run | It is not self-contained — it referenced the workspace it was written in |
553
- | Recollections are large and unhelpful | `autoRecall.limit` is too high, or node texts describe answers rather than questions |
554
- | The model recalls a route the API no longer has | Nothing re-checks memory against a rebuilt index. Supersede, and require verification |
555
- | A second run of the same project refuses to start | The directory lock. A lock whose process is gone is stale and is taken over |
610
+ | Symptom | Cause |
611
+ | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
612
+ | The `memory_*` tools are missing | No `memory:` on the agent. They come from the binding, never from `tools:` |
613
+ | `zen memory` says the project has none | Neither a `memory:` block nor an agent binding - nothing is opened and no directory is made |
614
+ | Recall finds a memory only when reworded | No embedder. `zen memory stats` says `embedding none` |
615
+ | Recall finds nothing after changing the embedder | Refused rather than mixed - a manifest records the model. Re-embed or change it back |
616
+ | Vectors fewer than nodes | Some nodes were committed with no embedder; they are only reachable by term overlap |
617
+ | The graph fills with restated requests | The commit rule is not in a prompt or skill. State the "would a later run redo this" test |
618
+ | An agent greps `/memory` or reads `graph.json` | The house-rules block is missing from `agents/memory-instructions.md` - the system prompt never forbids it |
619
+ | A recalled fact cannot be checked or continued | Nodes were committed with no provenance. The block's "say where it came from" rule is what prevents it |
620
+ | Recall returns pointers and the run re-reads it all | Content was committed as `text` alone. A node's text describes and locates; the content belongs in a file |
621
+ | A wrong memory keeps coming back | It was edited instead of superseded, or superseded in the wrong direction - the **new** node is the source |
622
+ | An agent cannot see a node you can | Its `audience` is not in that agent's `sees`. Invisible and missing are the same thing, on purpose |
623
+ | "this agent cannot remember files" | The agent has no workspace, so a path cannot be resolved. Commit without `file` |
624
+ | A remembered file will not run | It is not self-contained - it referenced the workspace it was written in |
625
+ | Recollections are large and unhelpful | `autoRecall.limit` is too high, or node texts describe answers rather than questions |
626
+ | The model recalls a route the API no longer has | Nothing re-checks memory against a rebuilt index. Supersede, and require verification |
627
+ | A second run of the same project refuses to start | The directory lock. A lock whose process is gone is stale and is taken over |
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Whether this project's memory house rules are still the rules.
4
+ #
5
+ # check-instructions.sh diff the copy against the reference
6
+ # check-instructions.sh diff the same, showing every differing line
7
+ # check-instructions.sh fix overwrite the copy from the reference
8
+ #
9
+ # `agents/memory-instructions.md` is a *copy* of the `zen-memory` skill's
10
+ # `references/memory-instructions.md`, and the reference is rewritten by every
11
+ # `zen init` and `zen open`. So the copy goes stale silently, and it drifts by
12
+ # trailing whitespace inside tables, which no one can see by reading. That is
13
+ # the whole reason this is a script and not a checklist line.
14
+ #
15
+ # Run it from anywhere; it finds the project root from its own location.
16
+
17
+ set -eu
18
+ cd "$(dirname "$0")/../../../.."
19
+
20
+ COPY=agents/memory-instructions.md
21
+ REFERENCE=.github/skills/zen-memory/references/memory-instructions.md
22
+ REMEDY="cp $REFERENCE $COPY"
23
+
24
+ if [ ! -f "$REFERENCE" ]; then
25
+ echo "check-instructions.sh: no $REFERENCE in $PWD" >&2
26
+ echo "check-instructions.sh: run \`zen open\` to rewrite the .github tree" >&2
27
+ exit 2
28
+ fi
29
+
30
+ # The project turns memory on when something declares it — the top-level block
31
+ # or an agent's binding — so any `memory:` key in the configuration counts.
32
+ # The config is found by name, in the order a run finds it.
33
+ config() {
34
+ for file in agents.yaml agents.yml agents/agents.yaml agents/agents.yml; do
35
+ if [ -f "$file" ]; then
36
+ echo "$file"
37
+ return 0
38
+ fi
39
+ done
40
+ }
41
+
42
+ uses_memory() {
43
+ # `|| true` because no configuration at all is an answer, not a failure.
44
+ conf="$(config || true)"
45
+ if [ -z "$conf" ]; then
46
+ return 1
47
+ fi
48
+ grep -Eq '^[[:space:]]*(-[[:space:]]+)?memory:' "$conf"
49
+ }
50
+
51
+ # The file with every line's trailing whitespace removed. Two of these being
52
+ # equal is the drift nobody can see by reading, named as such.
53
+ trimmed() {
54
+ sed 's/[[:space:]]*$//' "$1"
55
+ }
56
+
57
+ report() {
58
+ if uses_memory; then
59
+ echo "memory: on"
60
+ else
61
+ echo "memory: off"
62
+ fi
63
+
64
+ if [ ! -f "$COPY" ]; then
65
+ if uses_memory; then
66
+ echo "missing: $COPY"
67
+ echo "remedy: $REMEDY"
68
+ return 1
69
+ fi
70
+ echo "ok: no copy, and nothing needs one"
71
+ return 0
72
+ fi
73
+
74
+ if cmp -s "$REFERENCE" "$COPY"; then
75
+ if ! uses_memory; then
76
+ # Harmless, but it is rules for a capability nothing turned on.
77
+ echo "note: the copy is current, but no agent has a memory: binding"
78
+ fi
79
+ echo "ok: $COPY is the reference, byte for byte"
80
+ return 0
81
+ fi
82
+
83
+ echo "drift: $COPY differs from the reference"
84
+ # Said out loud because it is invisible on screen, and is the usual cause:
85
+ # a trailing space inside a table, or a lost final newline.
86
+ if [ "$(trimmed "$REFERENCE")" = "$(trimmed "$COPY")" ]; then
87
+ echo "drift: trailing whitespace only — nothing you could have seen"
88
+ fi
89
+ echo "remedy: $REMEDY"
90
+ return 1
91
+ }
92
+
93
+ show_diff() {
94
+ report || true
95
+ # `-L` names the two sides, since both files are called the same thing.
96
+ diff -u -L reference -L "$COPY" "$REFERENCE" "$COPY" || true
97
+ # The report's verdict, not the diff's.
98
+ report >/dev/null
99
+ }
100
+
101
+ fix() {
102
+ cp "$REFERENCE" "$COPY"
103
+ echo "wrote: $COPY"
104
+ echo "note: project policy belongs in agents/memory-policy-instructions.md"
105
+ }
106
+
107
+ case "${1:-status}" in
108
+ status) report ;;
109
+ diff) show_diff ;;
110
+ fix) fix ;;
111
+ *)
112
+ echo "usage: check-instructions.sh [status|diff|fix]" >&2
113
+ exit 2
114
+ ;;
115
+ esac