@zenera/cli 1.1.16 → 1.1.18

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 (71) hide show
  1. package/README.md +110 -19
  2. package/dist/banner.d.ts +19 -0
  3. package/dist/banner.js +12 -9
  4. package/dist/catalog.js +19 -20
  5. package/dist/command.d.ts +3 -0
  6. package/dist/commands/check.js +5 -1
  7. package/dist/commands/index.js +4 -1
  8. package/dist/commands/init.js +42 -7
  9. package/dist/commands/inspect.js +77 -13
  10. package/dist/commands/key.js +49 -1
  11. package/dist/commands/memory.js +16 -3
  12. package/dist/commands/meta.d.ts +3 -0
  13. package/dist/commands/meta.js +596 -0
  14. package/dist/commands/run.js +64 -35
  15. package/dist/engine.d.ts +15 -0
  16. package/dist/engine.js +26 -1
  17. package/dist/env.d.ts +20 -0
  18. package/dist/env.js +65 -0
  19. package/dist/home.d.ts +2 -0
  20. package/dist/home.js +2 -0
  21. package/dist/keys.d.ts +2 -0
  22. package/dist/keys.js +2 -0
  23. package/dist/main.js +1 -1
  24. package/dist/meta.d.ts +221 -0
  25. package/dist/meta.js +695 -0
  26. package/dist/podman.js +64 -2
  27. package/dist/projects.d.ts +9 -1
  28. package/dist/projects.js +19 -2
  29. package/dist/resolve.js +7 -1
  30. package/dist/sandbox.d.ts +11 -0
  31. package/dist/sandbox.js +24 -4
  32. package/dist/scaffold.d.ts +6 -0
  33. package/dist/scaffold.js +56 -15
  34. package/dist/session.d.ts +10 -0
  35. package/dist/session.js +19 -1
  36. package/dist/term.d.ts +7 -0
  37. package/dist/term.js +8 -1
  38. package/dist/tui/app.js +33 -5
  39. package/dist/tui/wrap.d.ts +1 -1
  40. package/dist/tui/wrap.js +1 -1
  41. package/dist/validate.js +57 -15
  42. package/package.json +3 -2
  43. package/templates/editor/.github/copilot-instructions.md +313 -94
  44. package/templates/editor/.github/prompts/project-review.prompt.md +74 -0
  45. package/templates/editor/.github/prompts/spec-apply-feedback.prompt.md +142 -0
  46. package/templates/editor/.github/prompts/spec-sync-project.prompt.md +392 -0
  47. package/templates/editor/.github/skills/zen-cli/references/check.md +6 -3
  48. package/templates/editor/.github/skills/zen-cli/references/frame.md +5 -3
  49. package/templates/editor/.github/skills/zen-cli/references/inspect.md +4 -3
  50. package/templates/editor/.github/skills/zen-cli/references/memory.md +14 -0
  51. package/templates/editor/.github/skills/zen-cli/references/projects.md +19 -2
  52. package/templates/editor/.github/skills/zen-cli/references/run.md +75 -16
  53. package/templates/editor/.github/skills/zen-cli/references/sandbox.md +6 -0
  54. package/templates/editor/.github/skills/zen-memory/SKILL.md +153 -33
  55. package/templates/editor/.github/skills/zen-memory/scripts/check-instructions.sh +115 -0
  56. package/templates/editor/.github/skills/zen-review/SKILL.md +123 -0
  57. package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +119 -0
  58. package/templates/editor/.github/skills/zen-review/scripts/review.sh +104 -0
  59. package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +197 -0
  60. package/templates/editor/.github/skills/zen-spec-sync/scripts/snapshot.sh +200 -0
  61. package/templates/editor/.vscode/settings.json +44 -4
  62. package/templates/parts/memory-terms.yaml.tmpl +16 -0
  63. package/templates/parts/memory.yaml.tmpl +12 -0
  64. package/templates/project/SPECIFICATION.md.tmpl +32 -2
  65. package/templates/project/agents/memory-instructions.md +278 -0
  66. package/templates/project/agents.yaml.tmpl +12 -3
  67. package/templates/project/env +28 -0
  68. package/templates/project/gitignore +4 -4
  69. package/templates/editor/.github/prompts/review-project.prompt.md +0 -36
  70. package/templates/editor/.github/prompts/sync-with-spec.prompt.md +0 -205
  71. /package/templates/project/{INSTRUCTIONS.md → agents/instructions.md} +0 -0
@@ -29,6 +29,7 @@ skill.
29
29
  | Flag | Meaning |
30
30
  | ----------------------- | -------------------------------------------- |
31
31
  | `--project <name\|dir>` | Which project. Inferred from the directory |
32
+ | `--dir <dir>` | Read this memory directory instead |
32
33
  | `--kind <name>` | Only this kind of node |
33
34
  | `--audience <name>` | Only nodes committed under this label |
34
35
  | `--files` | Only nodes that remember a file |
@@ -38,6 +39,19 @@ skill.
38
39
  | `--open` | Open the exported page |
39
40
  | `--yes` | Do not ask before removing |
40
41
 
42
+ ## A graph that is not the project's
43
+
44
+ `--dir` opens a memory directory as it stands, and skips project resolution
45
+ entirely: there need be no `agents.yaml` anywhere above it. That is what to use
46
+ for a graph a run was pointed at with `zen run --memory <dir>`, and for a copy
47
+ taken out of a running session - copy the directory, delete its `.lock`, and
48
+ read the copy while the run continues.
49
+
50
+ ```
51
+ zen run --memory .tmp/mem "…" remember into .tmp/mem for this run
52
+ zen memory --dir .tmp/mem stats then read that graph, from anywhere
53
+ ```
54
+
41
55
  ## It reads unmasked, on purpose
42
56
 
43
57
  Memory is masked per agent: a node's `audience` decides who can see it. Recall
@@ -19,18 +19,30 @@ What it writes:
19
19
 
20
20
  ```
21
21
  zenera.json { version, name }
22
- INSTRUCTIONS.md house rules
23
22
  agents.yaml the configuration
23
+ agents/instructions.md house rules, prepended to every agent
24
+ agents/memory-instructions.md more of them, about the memory store
24
25
  agents/prompts/default.md the default agent's prompt
25
26
  agents/skills/ empty, for skills
26
27
  assets/README.md
27
28
  sandbox/Dockerfile the image the sandbox builds
28
29
  sessions/ empty
30
+ .env this project's environment, git-ignored
29
31
  .gitignore
30
32
  .vscode/settings.json editor files
31
33
  .github/ copilot instructions, prompts, this skill
32
34
  ```
33
35
 
36
+ More house rules can be added by hand: any `agents/<topic>-instructions.md` is
37
+ read too, in filename order, and prepended to every agent. A capability that
38
+ needs standing rules - memory, say - gets its own file rather than another
39
+ section of `agents/instructions.md`. Memory's are not written from scratch:
40
+ `agents/memory-instructions.md` is the same file as
41
+ `.github/skills/zen-memory/references/memory-instructions.md`, which init puts
42
+ in the project as well, so a copy that was lost or went stale is restored rather
43
+ than rewritten - `.github/skills/zen-memory/scripts/check-instructions.sh` says
44
+ which of the two it is, and `… fix` does the copy.
45
+
34
46
  The project's own files are never overwritten - `--force` is what allows
35
47
  writing into an occupied directory, and the files already there stay. The
36
48
  editor files are the exception: `.vscode/settings.json` and the `.github/` tree
@@ -38,7 +50,12 @@ are ours and are replaced on every `init` and every `zen open`, so edits to them
38
50
  do not survive.
39
51
 
40
52
  The default agent gets the file tools and a sandboxed shell, plus `exa:*` when
41
- the keyring holds an Exa key.
53
+ the keyring holds an Exa key. It also gets memory: the store is scaffolded in
54
+ `memory/`, the agent is bound to it with `memory: true`, and it is vectorised by
55
+ the chosen provider's embedding model. A provider that publishes none -
56
+ Anthropic - gets the store without an `embeddings:` entry, and recall ranks by
57
+ term overlap until one is added; an entry naming a provider with no embeddings
58
+ API is a project that fails to load, not one that degrades.
42
59
 
43
60
  **Choosing the model.** Without `--model`, the keyring is asked - not counted.
44
61
  Stored credentials are probed, because holding a key is not the same as holding
@@ -9,24 +9,78 @@ answers once on stdout and exits.
9
9
 
10
10
  ## Options
11
11
 
12
- | Flag | What it does |
13
- | ----------------------- | ------------------------------------------------------ |
14
- | `--project <name\|dir>` | Which project. Inferred from the directory otherwise |
15
- | `--session <id>` | Continue a particular session |
16
- | `--new` | Start a fresh one |
17
- | `--workspace <dir>` | What the agent may read and write |
18
- | `--model <ref>` | Override the default model for this run |
19
- | `--image <ref>` | Override the container image commands run in |
20
- | `--read-only` | Withhold every tool that can write |
21
- | `--quiet` | The answer only; no narration |
22
- | `--plain` | One shot, even on a terminal |
23
- | `--theme <dark\|light>` | Force the palette. Detected otherwise; `$ZENERA_THEME` |
24
- | `--out <file>` | Write the answer to a file as well as to stdout |
25
- | `--yes` | Accept the questions it would otherwise ask |
12
+ | Flag | What it does |
13
+ | ----------------------- | -------------------------------------------------------- |
14
+ | `--project <name\|dir>` | Which project. Inferred from the directory otherwise |
15
+ | `--session <id>` | Continue a particular session |
16
+ | `--new` | Start a new session rather than continuing one |
17
+ | `--workspace <dir>` | What the agent may read and write |
18
+ | `--memory <dir>` | Where the agents remember into, instead of the project's |
19
+ | `--model <ref>` | Override the default model for this run |
20
+ | `--image <ref>` | Override the container image commands run in |
21
+ | `--read-only` | Withhold every tool that can write |
22
+ | `--plain` | Never open the TUI; a prompt is then required |
23
+ | `--theme <dark\|light>` | Palette for the TUI; `auto` detects. `$ZENERA_THEME` |
24
+ | `--out <file>` | Put the answer in this file instead of on stdout |
25
+ | `--yes` | Say yes to the workspace and install questions |
26
26
 
27
27
  Flags always beat the file: the repository states intent, the invocation
28
28
  overrides it.
29
29
 
30
+ ## Where the answer goes
31
+
32
+ The answer goes to stdout and the progress to stderr, so `zen run "…" >
33
+ answer.md` leaves a file with the answer in it and nothing else, watched or not.
34
+ `--out` is a destination rather than a copy: it takes the answer off stdout, so
35
+ the file is the only place it lands.
36
+
37
+ ```
38
+ zen run "what changed?" > out.md answer in the file, progress on screen
39
+ zen run "what changed?" > out.md 2>/dev/null ... and nothing on screen at all
40
+ zen run --out out.md "what changed?" the same file, stdout left empty
41
+ ```
42
+
43
+ There is no `--quiet`. Narration is on stderr already, so a redirect is enough;
44
+ `--json` silences it for machine use.
45
+
46
+ `--json` answers with the paths as well as the answer: the session and run
47
+ directories, every file the run left behind, and every tree the agent could see,
48
+ with the path it had on this machine beside the path it had inside the
49
+ container.
50
+
51
+ ```json
52
+ {
53
+ "session": { "id": "...", "dir": "<project>/sessions/<id>" },
54
+ "run": {
55
+ "id": "...",
56
+ "dir": "<project>/sessions/<id>/runs/<run-id>",
57
+ "input": ".../input.md",
58
+ "output": ".../output.md",
59
+ "state": ".../state.json",
60
+ "meta": ".../meta.json",
61
+ "report": ".../report.html"
62
+ },
63
+ "mounts": [
64
+ { "host": "...", "at": "/workspace", "readOnly": false },
65
+ { "host": "...", "at": "/assets", "readOnly": true },
66
+ { "host": "...", "at": "/skills/<name>", "readOnly": true }
67
+ ],
68
+ "agent": "...",
69
+ "stopReason": "final",
70
+ "durationMs": 0,
71
+ "usage": {},
72
+ "output": "the answer"
73
+ }
74
+ ```
75
+
76
+ `mounts` also lists the container's `$HOME` and, when keys are forwarded, the
77
+ credential files mounted under `/run/zenera/keys`.
78
+
79
+ `--plain` never opens the TUI. The TUI is the only thing that can ask for a
80
+ prompt, so with `--plain` the prompt has to be there already - in the argument
81
+ or on stdin - and `zen run --plain` with neither is an error instead of a
82
+ window. Say it in a script that must not meet one.
83
+
30
84
  ## Which word is the project
31
85
 
32
86
  The first positional is read as a project when it names one, and as the first
@@ -42,7 +96,7 @@ zen run --project why "why?" when the project is called "why"
42
96
  The prompt comes from the argument, or from stdin, or from the TUI:
43
97
 
44
98
  ```
45
- git diff | zen run --quiet "summarise this diff"
99
+ git diff | zen run "summarise this diff"
46
100
  ```
47
101
 
48
102
  ## What a prompt on the command line implies
@@ -56,10 +110,15 @@ there is someone to ask, still asks.
56
110
  There is no `resume`. A session continues itself, because its state is what it
57
111
  is; `--session <id>` picks which one.
58
112
 
113
+ `--new` only matters when there is no prompt. A prompt on the command line
114
+ already starts a new session, so `zen run --new "what changed?"` is the same as
115
+ `zen run "what changed?"`. Without a prompt the TUI asks which session to
116
+ continue, and `--new` is how you skip that question. It contradicts `--session`.
117
+
59
118
  ## The TUI
60
119
 
61
120
  Drawn only when there is a terminal on both stdin and stdout, no prompt,
62
- no `--plain`, no `--quiet` and no `--json`. It streams the answer, shows
121
+ no `--plain` and no `--json`. It streams the answer, shows
63
122
  reasoning as it arrives, and reports per-turn and per-session token usage in the
64
123
  footer.
65
124
 
@@ -47,6 +47,12 @@ Top level, and again per agent, where an agent's block is merged over the base.
47
47
  `CREDENTIAL`: the keyring has already materialised real credentials into the
48
48
  process environment, and a sandbox is the last place they should be forwarded to.
49
49
 
50
+ The door for those is the project's `.env`, which `zen init` writes and
51
+ `.gitignore` covers. `zen run` reads it before loading the project and forwards
52
+ every name in it into the container **by name**, so the value never reaches an
53
+ argv. `keys: false` and `zen run --no-keys` withhold it along with the API keys.
54
+ The shell's environment wins over the file; the keyring fills in the rest.
55
+
50
56
  ## `persist: true` is usually what you want
51
57
 
52
58
  Only `/workspace` and `/home/agent` survive an ephemeral container. A
@@ -1,6 +1,6 @@
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
@@ -197,8 +197,9 @@ below.
197
197
  └── .lock held while a run is writing; a dead pid's lock is stale
198
198
  ```
199
199
 
200
- It is project state, not source, and it is not committed. The directory is
201
- mounted **read-only** at `/memory` in the agent's namespace.
200
+ It is committed with the project - everything but `.lock`, which is runtime
201
+ state. The directory is mounted **read-only** at `/memory` in the agent's
202
+ namespace.
202
203
 
203
204
  ## Designing a memory strategy
204
205
 
@@ -363,6 +364,40 @@ lands at `<project>/memory/files/<id>.<ext>` and appears to the agent as:
363
364
  | Mount | `/memory`, **read-only** |
364
365
  | Extension | preserved, because it decides how the file runs and renders |
365
366
 
367
+ ### The agent has to be able to write the file first
368
+
369
+ `memory_commit` **copies** a file that already exists. It is not an upload
370
+ channel and it cannot create one. So everything too big for a node's `text` - a
371
+ script, a config, an assembled passage - depends on the agent having a tool that
372
+ puts bytes in the **workspace**, and an agent with none can only ever commit
373
+ text no matter how much the memory design leans on `file` nodes.
374
+
375
+ | The agent's `tools:` | Can it commit a `file`? |
376
+ | ---------------------------- | ----------------------------------------------------------------------- |
377
+ | `write_file` / `apply_patch` | Yes. Write it, then name the same path |
378
+ | `run_command` only | Yes, but only under `/workspace` - a heredoc into `/tmp` is unreachable |
379
+ | Neither | No. Everything worth keeping has to fit on one line of `text` |
380
+
381
+ The workspace is the only tree that round-trips, because it is the only
382
+ writable one. `/assets`, `/skills` and `/memory` resolve as well - they are
383
+ mounts the file tools know by name - but they are **read-only**, so nothing can
384
+ be put there to be remembered. Every other absolute path is real to
385
+ `run_command` and outside the workspace root, so a file written to `/tmp` comes
386
+ back as:
387
+
388
+ ```
389
+ error: outside the workspace: /tmp/report.py
390
+ ```
391
+
392
+ A bare error carrying none of the `hint` the other memory refusals do, because
393
+ containment is a workspace answer rather than a memory one.
394
+
395
+ This is the trap in a `tools:` list trimmed to `run_command` alone: the shell
396
+ can create the file, so `file` nodes look supported, but nothing in the agent's
397
+ context ever uses workspace vocabulary and the model has no reason to prefer
398
+ `/workspace` over `/tmp`. Either give it the file tools, or say in its prompt
399
+ that work it means to keep goes in `/workspace`.
400
+
366
401
  Reusing one is the point of keeping it:
367
402
 
368
403
  ```
@@ -379,11 +414,14 @@ node explaining what it is and the graph cannot acquire orphans. To change a
379
414
  remembered file: copy it into the workspace, edit it there, commit the new one
380
415
  and link it with `SUPERSEDES` to the old.
381
416
 
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 |
417
+ The node text describes and locates; the file carries the content. Reach for a
418
+ node with no file only when the whole of what you know fits on one line.
419
+
420
+ | Commit as | When |
421
+ | --------- | --------------------------------------------------------------------------- |
422
+ | `file` | It is content - a script, a config, a query, a passage, an assembled answer |
423
+ | `snippet` | It is a few lines that only make sense read, and nothing will ever run them |
424
+ | `fact` | It is one line. A file containing one sentence is a fact in a costume |
387
425
 
388
426
  ## Memory in front of a `zen rag` index
389
427
 
@@ -400,18 +438,26 @@ it is entirely a matter of **what you commit**.
400
438
  | Freshness | as fresh as the last `index` | as stale as it was allowed to get |
401
439
  | Cost of a query | one embedding round trip (`search`), or nothing (`list`/`grep`) | already in the prompt |
402
440
 
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.
441
+ So: **never copy content out of an index that still changes.** A schema pasted
442
+ into a node goes stale silently and the index already answers in milliseconds; a
443
+ documentation passage quoted into a node outlives the release it was true for
444
+ and will look exactly as authoritative when it is wrong.
445
+
446
+ A **pinned** index is the exception, and a real one. Docs indexed from a
447
+ released version or a versioned reference tree cannot change under the memory,
448
+ so a passage taken out of one stays true as long as the project stays on that
449
+ version. Keep it as a `file` node whose text names the documents and line ranges
450
+ and says which version it was. That is as well as the route, never instead of
451
+ it.
407
452
 
408
- What memory should hold is the **route** - the resolution, not the source:
453
+ What memory should hold first is the **route** - the resolution, not the source:
409
454
 
410
455
  | Commit | Kind | Because |
411
456
  | --------------------------------------------------------------------------------- | ------------------ | --------------------------------------------------------------------------------- |
412
457
  | 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
458
  | The sequence of calls that was necessary | `plan` | No ranking finds an ordering. It is not in the document |
414
459
  | Where the answer lives in the docs - document name, heading, line range | `fact` | `zen rag docs show` reads it back verbatim and current |
460
+ | 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 |
415
461
  | 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
462
  | Where the index itself is | `fact` | `-d` or `$ZEN_SCHEMA_DB`. An agent cannot guess a path |
417
463
  | A working request body, or a script that drives the API | `snippet` / `file` | The thing you would otherwise write again |
@@ -451,8 +497,12 @@ cheap and settles it.
451
497
  ### The project skill is where this is written down
452
498
 
453
499
  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:
500
+ `zen-rag-schema` and `zen-rag-docs`. The memory policy that is **about the
501
+ index** belongs in that skill, because it is the same subject and it is read at
502
+ the moment the index is. Policy that holds whatever the agent is doing -
503
+ audiences, what must never be written down - goes in
504
+ `agents/memory-policy-instructions.md` instead, which every agent gets whether
505
+ it loads the skill or not.
456
506
 
457
507
  ```md
458
508
  - Recalled routes are a shortcut, not authority. Confirm with `list_api --name`
@@ -460,10 +510,61 @@ because it is the same subject:
460
510
  - After solving something the index made you work for, commit it: the
461
511
  `operation` you ended on, linked to the `task` that asked and any `fact` you
462
512
  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`.
513
+ - Never commit schema text or documentation passages copied out of a document
514
+ that still changes. Commit the pointer - document, heading, line range -
515
+ alongside what you concluded from it, and read the passage back with
516
+ `zen rag docs show` to check the conclusion still holds.
517
+ - Our docs are indexed from the pinned 1.4 tree, so a passage out of them is
518
+ safe to keep whole: commit it as a `file` node headed with the documents and
519
+ line ranges, next to the pointer and the conclusion.
520
+ ```
521
+
522
+ ## The house rules
523
+
524
+ **Every project that enables memory carries `references/memory-instructions.md`
525
+ in its own `agents/memory-instructions.md`**, as part of turning memory on. That
526
+ file is how the agent is told to use the store: read and search it only through
527
+ the tools, never through `/memory` itself, and what a node must carry to still be
528
+ worth having.
529
+
530
+ `zen init` writes it, because the projects it scaffolds have memory on from the
531
+ first run. Two cases are left to check by hand: a project that turned memory on
532
+ afterwards, and one whose copy has drifted from the reference or was deleted.
533
+
534
+ Neither is checked by eye. The skill ships the check, and it is one command:
535
+
536
+ ```sh
537
+ .github/skills/zen-memory/scripts/check-instructions.sh # the verdict
538
+ .github/skills/zen-memory/scripts/check-instructions.sh diff # every differing line
539
+ .github/skills/zen-memory/scripts/check-instructions.sh fix # copy the reference over
540
+ ```
541
+
542
+ It says whether this project turns memory on at all, exits non-zero on a copy
543
+ that is missing or stale, and prints the `cp` that repairs it. When the only
544
+ difference is trailing whitespace it says so - that is the usual drift, it is
545
+ invisible on screen, and it is the reason reading the two files side by side
546
+ never caught it.
547
+
548
+ It is a copy, not a transcription - this reference already sits in the project,
549
+ because `zen init` and `zen open` write the whole `.github/` tree, and they write
550
+ it from the same file the project's copy came from. What `fix` runs, from the
551
+ project root, is exactly:
552
+
553
+ ```sh
554
+ cp .github/skills/zen-memory/references/memory-instructions.md agents/memory-instructions.md
465
555
  ```
466
556
 
557
+ Everything the project keeps under `agents/` named `<topic>-instructions.md` is
558
+ prepended to every agent, in filename order, so this belongs in its own document
559
+ rather than as another section of `agents/instructions.md` - it arrives with
560
+ memory and it leaves with it.
561
+
562
+ **Keep that file a pure copy.** Project-specific policy - audiences, what must
563
+ never be written down, what an agent commits at the end of a job - goes in
564
+ `agents/memory-policy-instructions.md`, which filename order puts directly after
565
+ it. Then keeping up with a changed reference is the same one command again,
566
+ rather than a hand-patch around prose that has to be preserved.
567
+
467
568
  ## Inspecting and repairing it
468
569
 
469
570
  ```
@@ -486,9 +587,17 @@ so inspection is free and offline.
486
587
  | Why was that recalled? | `zen memory show <id>` |
487
588
  | That should never have been written down | `zen memory forget <id>` |
488
589
 
590
+ The directory is not fixed. `zen run --memory <dir>` sends one run's memory
591
+ somewhere else - a scratch graph for a trial, one per branch, or a shared one
592
+ outside the repository - and `zen memory --dir <dir>` reads any such directory
593
+ with no project around it. Together they are also how you read a graph while a
594
+ run holds its `.lock`: copy the directory, delete the copy's `.lock`, and point
595
+ `--dir` at the copy.
596
+
489
597
  `zen inspect` answers the other half: a run report shows the recollection block
490
598
  exactly as the model received it, which is how you tell "memory had nothing"
491
- apart from "memory had it and the model ignored it".
599
+ apart from "memory had it and the model ignored it". A run given `--memory`
600
+ wants `zen inspect --memory <dir>` to rebuild its report against the same graph.
492
601
 
493
602
  ## A worked configuration
494
603
 
@@ -524,10 +633,16 @@ agents:
524
633
 
525
634
  ## Review checklist
526
635
 
636
+ - [ ] `.github/skills/zen-memory/scripts/check-instructions.sh` exits zero, so
637
+ `agents/memory-instructions.md` is the reference byte for byte - and
638
+ project policy lives in `agents/memory-policy-instructions.md`.
527
639
  - [ ] `memory.embedding` is set, and was set before the graph had content.
528
640
  - [ ] Every agent that should learn has a `memory:` binding - the top-level
529
641
  block alone enables nothing.
530
642
  - [ ] `access: full` appears at most once.
643
+ - [ ] Any agent the design expects to commit `file` nodes can write to the
644
+ workspace - `write_file`/`apply_patch`, or `run_command` with `/workspace`
645
+ named in its prompt. Nothing else round-trips.
531
646
  - [ ] `sees`/`writes` exist because an agent would be **misled** by the other
532
647
  slice, not merely because it does not need it.
533
648
  - [ ] `autoRecall: false` on any agent whose user message is a payload.
@@ -538,18 +653,23 @@ agents:
538
653
 
539
654
  ## When it goes wrong
540
655
 
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 |
656
+ | Symptom | Cause |
657
+ | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
658
+ | The `memory_*` tools are missing | No `memory:` on the agent. They come from the binding, never from `tools:` |
659
+ | `zen memory` says the project has none | Neither a `memory:` block nor an agent binding - nothing is opened and no directory is made |
660
+ | Recall finds a memory only when reworded | No embedder. `zen memory stats` says `embedding none` |
661
+ | Recall finds nothing after changing the embedder | Refused rather than mixed - a manifest records the model. Change it back, or start a new memory |
662
+ | Vectors fewer than nodes | Some nodes were committed with no embedder; they are only reachable by term overlap |
663
+ | 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 |
664
+ | 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 |
665
+ | 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 |
666
+ | 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 |
667
+ | A wrong memory keeps coming back | It was edited instead of superseded, or superseded in the wrong direction - the **new** node is the source |
668
+ | 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 |
669
+ | "this agent cannot remember files" | The agent has no workspace, so a path cannot be resolved. Commit without `file` |
670
+ | `error: outside the workspace: /tmp/…` | The file is real to the shell and outside the only tree memory reads. Write it under `/workspace` |
671
+ | Long content arrives as `text`, never as a `file` | The agent has no tool that writes to the workspace, so it has no path to name |
672
+ | A remembered file will not run | It is not self-contained - it referenced the workspace it was written in |
673
+ | Recollections are large and unhelpful | `autoRecall.limit` is too high, or node texts describe answers rather than questions |
674
+ | The model recalls a route the API no longer has | Nothing re-checks memory against a rebuilt index. Supersede, and require verification |
675
+ | 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