@zenera/cli 1.1.23 → 1.1.24

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 +35 -2
  2. package/dist/ask.d.ts +51 -0
  3. package/dist/ask.js +143 -0
  4. package/dist/batch.d.ts +77 -0
  5. package/dist/batch.js +338 -0
  6. package/dist/command.d.ts +6 -0
  7. package/dist/commands/inspect.d.ts +11 -0
  8. package/dist/commands/inspect.js +388 -89
  9. package/dist/commands/list.js +128 -19
  10. package/dist/commands/memory.js +342 -12
  11. package/dist/commands/models.js +14 -2
  12. package/dist/commands/run.js +139 -34
  13. package/dist/commands/sandbox.js +72 -1
  14. package/dist/engine.d.ts +19 -0
  15. package/dist/engine.js +60 -8
  16. package/dist/liveness.js +6 -3
  17. package/dist/main.js +5 -2
  18. package/dist/podman.d.ts +16 -0
  19. package/dist/podman.js +115 -2
  20. package/dist/progress.d.ts +33 -0
  21. package/dist/progress.js +402 -0
  22. package/dist/request.d.ts +45 -0
  23. package/dist/request.js +300 -0
  24. package/dist/sandbox.js +10 -0
  25. package/dist/session.d.ts +26 -1
  26. package/dist/session.js +42 -3
  27. package/dist/term.d.ts +3 -0
  28. package/dist/term.js +17 -1
  29. package/dist/trace.d.ts +84 -0
  30. package/dist/trace.js +686 -0
  31. package/dist/validate.js +25 -3
  32. package/package.json +2 -2
  33. package/templates/editor/.github/copilot-instructions.md +11 -6
  34. package/templates/editor/.github/prompts/finetune.prompt.md +251 -0
  35. package/templates/editor/.github/skills/zen-cli/SKILL.md +9 -3
  36. package/templates/editor/.github/skills/zen-cli/references/check.md +7 -5
  37. package/templates/editor/.github/skills/zen-cli/references/inspect.md +262 -21
  38. package/templates/editor/.github/skills/zen-cli/references/memory.md +89 -19
  39. package/templates/editor/.github/skills/zen-cli/references/models.md +1 -1
  40. package/templates/editor/.github/skills/zen-cli/references/projects.md +12 -5
  41. package/templates/editor/.github/skills/zen-cli/references/run.md +201 -4
  42. package/templates/editor/.github/skills/zen-finetune/SKILL.md +1000 -0
  43. package/templates/editor/.github/skills/zen-finetune/scripts/collect.sh +432 -0
  44. package/templates/editor/.github/skills/zen-finetune/scripts/sample.sh +199 -0
  45. package/templates/editor/.github/skills/zen-inspect/SKILL.md +396 -0
  46. package/templates/editor/.github/skills/zen-memory/SKILL.md +74 -29
  47. package/templates/editor/.github/skills/zen-memory-warmup/SKILL.md +363 -531
  48. package/templates/editor/.github/skills/zen-sandbox/SKILL.md +19 -0
  49. package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +334 -0
  50. package/templates/editor/.github/skills/zen-sandbox-capacity/references/loadtest.md +181 -0
  51. package/templates/editor/.github/skills/zen-sandbox-capacity/scripts/preflight_sandbox.sh +457 -0
  52. package/templates/editor/.vscode/agents.schema.json +1 -1
  53. package/templates/editor/.vscode/settings.json +1 -0
  54. package/templates/project/agents/files-instructions.md +13 -0
  55. package/templates/project/agents/fork-instructions.md +27 -0
  56. package/templates/project/agents/memory-instructions.md +80 -7
  57. package/templates/project/gitignore +3 -0
@@ -1,188 +1,212 @@
1
1
  ---
2
2
  name: zen-memory-warmup
3
- description: Pre-populating an agent project's memory graph through warm-up queries before production or deployment — isolating scratch memory and workspaces in timestamped .tmp directories, using stronger reasoning models, validating machine-readable JSON output, categorizing queries with limits, shuffling, dry-run execution, fanning queries out across parallel runs and folding the results back with zen memory merge, inspecting the graph with zen memory stats/ls, atomic promotion into the project, and generating automated population scripts in scripts/memory_warmup.sh.
3
+ description: Pre-populating an agent project's memory graph before production by running a curated file of seed queries through `zen run batch` — one parallel batch instead of a hand-rolled fan-out, with per-item workspaces and per-item memories the command makes for you, choosing the source graph with --memory, bounding the parallelism with --concurrency, warming with a stronger --model, reading what each query learned from batch.json and per-item output.json, folding it all back with zen memory merge, validating with zen memory stats, atomic promotion into the project, and generating the population script in scripts/memory_warmup.sh.
4
4
  ---
5
5
 
6
6
  # Memory Warmup
7
7
 
8
- Memory warmup is the process of pre-populating an agent project's knowledge graph
9
- before deploying it or exposing it to end users.
8
+ Memory warmup is the process of pre-populating an agent project's knowledge
9
+ graph before deploying it or exposing it to end users.
10
10
 
11
- In Zenera Neo, an agent's memory graph (`memory/`) starts completely empty. A cold
12
- start means early runs cannot recall prior approaches, domain facts, verified
13
- plans, or tool schemas. Warming up the memory addresses this by executing a
14
- curated set of representative domain queries ahead of time, allowing agents to
15
- synthesize, connect, and commit high-value knowledge nodes.
11
+ In Zenera Neo an agent's memory graph (`memory/`) starts empty. A cold start
12
+ means early runs cannot recall prior approaches, domain facts, verified plans
13
+ or tool schemas. Warming up addresses this by asking a curated set of
14
+ representative domain questions ahead of time, so the agents synthesise, link
15
+ and commit high-value knowledge nodes before a user is waiting on them.
16
+
17
+ Warmup is one command: **`zen run batch`**. A file of questions goes in, a
18
+ directory of memories comes out, and `zen memory merge` folds them into one.
16
19
 
17
20
  ---
18
21
 
19
22
  ## Why Warm Up Memory?
20
23
 
21
- 1. **Eliminate Cold Starts**: Instead of failing or struggling through initial
22
- discovery, the agent recalls established patterns, known schemas, and
23
- proven plans on its very first user-facing turn.
24
- 2. **Higher-Quality Graph via Stronger Models**: You can run warmup turns using
25
- a more capable reasoning model (`--model`) than your day-to-day runtime
26
- model. A smarter model creates cleaner abstractions, links causal relations
27
- accurately, and produces superior plans.
28
- 3. **Consistency & Alignment**: Foundational conventions, domain boundaries,
29
- and operational rules are seeded as durable `fact` and `plan` nodes rather
30
- than rediscovered inconsistently across random user sessions.
24
+ 1. **Eliminate cold starts.** Instead of struggling through initial discovery,
25
+ the agent recalls established patterns, known schemas and proven plans on
26
+ its very first user-facing turn.
27
+ 2. **A better graph, from a stronger model.** Warmup runs are infrequent, so
28
+ they can afford a flagship reasoning model (`--model`) that the day-to-day
29
+ runtime model cannot. The superior structure it leaves behind is then
30
+ recalled by the cheaper model.
31
+ 3. **Consistency and alignment.** Conventions, domain boundaries and operating
32
+ rules are seeded as durable `fact` and `plan` nodes, rather than being
33
+ rediscovered inconsistently across random user sessions.
31
34
 
32
35
  ---
33
36
 
34
- ## Core Architecture & Safety Rules
37
+ ## What the Command Already Does
35
38
 
36
- Warmup is an offline staging process. It must adhere to six safety rules:
39
+ Warmup used to be a script's problem: a memory per run because the lock is per
40
+ directory, a fresh workspace per run, a bounded number of background jobs, and
41
+ a merge at the end. `zen run batch` is that fan-out made into a command, so
42
+ none of it is the script's problem any more.
37
43
 
38
- ### 1. Never warm up directly into the project's active `memory/`
44
+ | What warmup needs | What `zen run batch` does |
45
+ | :----------------------------------- | :-------------------------------------------------------------------------- |
46
+ | A memory no other run is writing | Each item gets its own copy at `<batch-dir>/<id>/memory` |
47
+ | The project's own graph left alone | It is the source to copy from, and is never written |
48
+ | A clean working directory per query | Each item gets `<batch-dir>/<id>/workspace` |
49
+ | Parallelism the machine can stand | `--concurrency <n>`, default 16, ceiling 32 |
50
+ | One bad query not costing the others | The failure is written to its `output.json`; the rest still answer |
51
+ | A record of what happened | `batch.json`, the index: every item, whether it worked, where its answer is |
52
+ | The fold back | It prints the `zen memory merge <batch-dir>/*/memory` line when it is done |
39
53
 
40
- If a warmup query fails, hallucinates, or aborts mid-turn, committing directly
41
- to the project's memory directory corrupts the knowledge graph. Always point
42
- `zen run` to a timestamped staging directory in `.tmp/` via `--memory` so that
43
- repeated or concurrent runs never collide:
54
+ What is left for the warmup script is the part that is actually about warmup:
55
+ **which questions to ask, which graph to start from, which model to ask with,
56
+ and whether the result is good enough to promote.**
44
57
 
45
- ```sh
46
- STAMP="$(date +%Y%m%d-%H%M%S)"
47
- --memory ".tmp/warmup-$STAMP/memory"
48
- ```
58
+ ---
49
59
 
50
- ### 2. Isolate workspaces in `.tmp/` with per-run timestamps
60
+ ## Core Safety Rules
51
61
 
52
- Warmup queries often instruct agents to investigate repositories, write test
53
- scripts, or run commands. Running them in the actual project or repository
54
- working directory risks modifying tracked files or creating untracked clutter.
62
+ ### 1. Never warm up into the project's active `memory/`
55
63
 
56
- Always isolate the workspace per run into a fresh, timestamped subdirectory
57
- under `.tmp/`:
64
+ A batch never writes the source graph - it copies it - so the default is
65
+ already safe. Keep the whole staging tree in `.tmp/` under a timestamp so that
66
+ repeated or concurrent warmups cannot collide, and so a failed one can be read
67
+ afterwards rather than being lost:
58
68
 
59
69
  ```sh
60
- --workspace ".tmp/warmup-$STAMP/workspaces/run-01-$(date +%s)"
70
+ STAMP="$(date +%Y%m%d-%H%M%S)"
71
+ zen run batch --input cases.json --batch-dir ".tmp/warmup-$STAMP/batch"
61
72
  ```
62
73
 
63
- Using timestamps on both the root staging directory and each individual run
64
- workspace ensures:
65
-
66
- - Concurrent or repeated warmup attempts never overwrite each other's state.
67
- - Stale or corrupted files from an earlier failed run cannot leak into subsequent
68
- queries.
69
- - Every turn begins from a completely pristine, deterministic working directory.
70
-
71
- ### 3. Use stronger models during population
72
-
73
- Day-to-day execution might use a fast, cost-effective model (e.g.
74
- `google:gemini-2.5-flash` or `anthropic:claude-3-5-haiku`). Warmup runs are
75
- performed infrequently, so it makes sense to use a flagship reasoning model
76
- (e.g. `anthropic:claude-sonnet-4-5`, `openai:gpt-5`, or
77
- `google:gemini-3.5-flash`):
74
+ Without `--batch-dir` the batch picks `<project>/batches/<stamp>` and prints
75
+ the path on stdout, which is the other way to write it:
78
76
 
79
77
  ```sh
80
- --model anthropic:claude-sonnet-4-5
78
+ DIR="$(zen run batch --input cases.json)"
81
79
  ```
82
80
 
83
- The superior graph structure created by the flagship model is then recalled and
84
- utilized by the runtime model.
81
+ ### 2. Choose the graph every item starts from
85
82
 
86
- ### 4. Execute non-interactively with `--json`
83
+ `--memory <dir>` names the source. There are two warmups, and they differ only
84
+ in what they point it at:
87
85
 
88
- Using `--json` guarantees:
86
+ | Warmup | Source | Every item starts from |
87
+ | :------------------------ | :-------------------------------- | :-------------------------- |
88
+ | Building on what is known | the project's `memory/` (default) | a copy of the current graph |
89
+ | From nothing | a directory that does not exist | an empty graph |
89
90
 
90
- - Interactive TUI and confirmation prompts are disabled (assumes `--yes`).
91
- - Progress narration on stderr is silenced.
92
- - Machine-readable JSON output on stdout containing `stopReason`, `usage`,
93
- `mounts`, run artifact paths (`run.dir`, `run.output`), and the answer.
94
- - The population script can programmatically verify whether the run finished
95
- successfully (`stopReason === "final"`).
91
+ A source that is not there yet is not an error in the copying mode: a project
92
+ declares its memory directory and the first run makes it, so a cold project is
93
+ warmed by copying from nothing, and nothing is what each item gets.
96
94
 
97
- ### 5. Validate before promotion
95
+ **`--memory-read-only` is not a warmup flag.** It means nobody writes, which is
96
+ exactly what a warmup is for. It is for asking a batch of questions _of_ a
97
+ warmed graph afterwards - sixteen runs recalling from the one memory, changing
98
+ nothing.
98
99
 
99
- Track graph accumulation by inspecting `zen memory stats --dir ...` in between
100
- warmup queries, and do not overwrite the project's `memory/` until the final
101
- graph in `.tmp/warmup-<timestamp>/memory` has been verified to contain expected
102
- nodes and vector embeddings.
100
+ ### 3. Use a stronger model during population
103
101
 
104
- ### 6. Give every parallel run its own memory directory
105
-
106
- The memory lock is **per directory**. Two runs pointed at the same `--memory`
107
- do not interleave — the second one refuses to start. So a warmup that wants to
108
- use the wall clock has to fan out into one memory per run:
102
+ Day-to-day execution might use a fast, cost-effective model (e.g.
103
+ `google:gemini-2.5-flash` or `anthropic:claude-3-5-haiku`). Warm up with a
104
+ flagship reasoning model instead:
109
105
 
110
106
  ```sh
111
- .tmp/warmup-$STAMP/memory-1
112
- .tmp/warmup-$STAMP/memory-2
113
- ...
107
+ --model anthropic:claude-sonnet-4-5
114
108
  ```
115
109
 
116
- and fold them back together afterwards with `zen memory merge`, which is
117
- offline, contacts no model, and preserves node ids:
110
+ It applies to every item in the batch.
118
111
 
119
- ```sh
120
- zen memory merge .tmp/warmup-$STAMP/memory-* --dir .tmp/warmup-$STAMP/memory
121
- ```
112
+ ### 4. Read the result before promoting it
113
+
114
+ The staging graph is inspected with `zen memory stats --dir ...`, and nothing
115
+ overwrites the project's `memory/` until it holds what it should. Because each
116
+ item kept its own memory, this can be done **per query** as well as in total -
117
+ see below.
118
+
119
+ ### 5. Promote atomically, and keep the old graph
122
120
 
123
- Merging is what makes `--incremental` safe under fan-out: every shard starts
124
- from a copy of the same project memory, so most of what comes back is a shared
125
- ancestor rather than a collision. Shared ancestors reconcile silently; two runs
126
- that genuinely changed the same memory in different ways stop the merge and
127
- name the ids, because picking a winner silently is how a merge loses an answer.
121
+ Back the current `memory/` up under a timestamp and move the staging graph into
122
+ place. A warmup that turns out to have been a bad idea is then one `mv` away
123
+ from being undone.
128
124
 
129
125
  ---
130
126
 
131
- ## Fanning Out
127
+ ## The Shape of a Warmup
132
128
 
133
129
  ```mermaid
134
130
  flowchart LR
135
- A["Selected queries"] --> B1["zen run → memory-1"]
136
- A --> B2["zen run → memory-2"]
137
- A --> B3["zen run → memory-N"]
138
- B1 --> M["zen memory merge memory-* --dir memory"]
139
- B2 --> M
140
- B3 --> M
131
+ Q["scripts/warmup-queries.json"] --> S["select: category, shuffle, limit"]
132
+ S --> C["cases.json"]
133
+ C --> B["zen run batch --concurrency 4 --model <flagship>"]
134
+ B --> I1["batch/architecture-01/memory"]
135
+ B --> I2["batch/workflows-01/memory"]
136
+ B --> I3["batch/.../memory"]
137
+ I1 --> M["zen memory merge batch/*/memory --dir memory"]
138
+ I2 --> M
139
+ I3 --> M
141
140
  M --> V["zen memory stats --dir memory"]
142
- V --> P["Backup & promote to memory/"]
141
+ V --> P["back up and promote to memory/"]
143
142
  ```
144
143
 
145
- Bound the fan-out with `--jobs <n>`. Each run is a model call **and** a Podman
146
- container, so the useful ceiling is set by the machine and the provider's rate
147
- limit rather than by the number of queries; 4 is a sane default.
148
-
149
- Sequential warmup is still the better choice when the queries build on each
150
- other, because a later query can recall what an earlier one committed. Fan-out
151
- trades that away for wall-clock time, and `merge` gives back everything except
152
- the cross-query recall.
153
-
154
- ---
155
-
156
- ## The Warmup Cycle
157
-
158
144
  ```mermaid
159
145
  flowchart TD
160
- A[Curated Seed Queries] --> B["Initialize .tmp/warmup-<timestamp>/memory"]
161
- B --> C["For each query: Create fresh .tmp/warmup-<timestamp>/workspaces/run-N-<timestamp>"]
162
- C --> D["zen run --memory .tmp/warmup-<timestamp>/memory --workspace .tmp/.../run-N-<timestamp> --model <model> --json"]
163
- D --> E{Check stopReason & exit code}
164
- E -- Success --> F[Accumulate nodes in staging graph]
165
- E -- Failure --> G[Log error & abort or retry]
166
- F --> T["Log timing: query duration & total elapsed"]
167
- T --> S["Inspect progress: zen memory stats --dir .tmp/warmup-<timestamp>/memory"]
168
- S --> H{More queries?}
169
- H -- Yes --> C
170
- H -- No --> I["Validate: zen memory stats --dir .tmp/warmup-<timestamp>/memory"]
171
- I --> J{Graph healthy?}
172
- J -- Yes --> K["Backup existing memory & promote .tmp/warmup-<timestamp>/memory to memory/"]
173
- J -- No --> L[Inspect with zen memory ls / export & discard]
146
+ A["Curated seed queries"] --> B["Select: --category / --shuffle / --limit"]
147
+ B --> C{"--dry-run?"}
148
+ C -- Yes --> D["Print the cases file and the command, write nothing, exit 0"]
149
+ C -- No --> E["Write .tmp/warmup-<stamp>/cases.json"]
150
+ E --> F["zen run batch --input cases.json --batch-dir .tmp/warmup-<stamp>/batch"]
151
+ F --> G{"Exit code"}
152
+ G -- Non-zero --> H["Name the failed ids from batch.json, promote nothing"]
153
+ G -- Zero --> I["zen memory merge batch/*/memory --dir .tmp/warmup-<stamp>/memory"]
154
+ I --> J["zen memory stats --dir .tmp/warmup-<stamp>/memory"]
155
+ J --> K{"Graph healthy?"}
156
+ K -- No --> L["Inspect per item, or with zen memory ls / export, and discard"]
157
+ K -- Yes --> M["Back up memory/ and promote"]
174
158
  ```
175
159
 
176
160
  ---
177
161
 
178
- ## Query Categories, Sampling & Dry-Run Preview
162
+ ## The Queries File
163
+
164
+ Keep the seed queries in the repository, in the shape the command already
165
+ reads, so warmup is reproducible across environments and model upgrades:
166
+
167
+ ```json
168
+ {
169
+ "batch": [
170
+ {
171
+ "id": "architecture-layout",
172
+ "input": "Investigate the project architecture and summarize the main components and data flow."
173
+ },
174
+ {
175
+ "id": "architecture-boundaries",
176
+ "input": "Analyze module boundaries, dependency graphs, and core interfaces."
177
+ },
178
+ {
179
+ "id": "workflows-triage",
180
+ "input": "Formulate standard operational plans for triaging and resolving common domain tasks."
181
+ },
182
+ {
183
+ "id": "integrations-services",
184
+ "input": "Identify external services, APIs, and key credential dependencies."
185
+ },
186
+ {
187
+ "id": "errors-handling",
188
+ "input": "Analyze error handling strategies, recovery routines, and failure modes."
189
+ },
190
+ {
191
+ "id": "conventions-house-rules",
192
+ "input": "Document project-specific coding standards, house rules, and commit policies."
193
+ }
194
+ ]
195
+ }
196
+ ```
197
+
198
+ An item takes `input`, an optional `id` and an optional `workspace`, and
199
+ nothing else. `memory`, `model` and `project` belong to the whole batch, so
200
+ they are flags on the line rather than keys in the file.
179
201
 
180
- Warmup suites should be organized across distinct domain categories rather than
181
- an unstructured flat list of ad-hoc prompts.
202
+ **The `id` is the category.** It names the directory the item's answer and
203
+ memory land in, so `architecture-layout` makes `batch/architecture-layout/` -
204
+ readable in a directory listing, greppable in `batch.json`, and selectable by
205
+ prefix. That is the whole of the category mechanism; no extra key is needed.
182
206
 
183
207
  ### Recommended Categories
184
208
 
185
- | Category | Objective | Example Prompt |
209
+ | Category | Objective | Example prompt |
186
210
  | :------------- | :----------------------------------------------------------- | :------------------------------------------------------------------------- |
187
211
  | `architecture` | Map high-level layout, component boundaries, and data flow | "Investigate project architecture and summarize component flow." |
188
212
  | `workflows` | Formulate multi-step operational plans for common tasks | "Formulate standard operational plans for triaging common tasks." |
@@ -190,495 +214,303 @@ an unstructured flat list of ad-hoc prompts.
190
214
  | `errors` | Capture error handling, recovery routines, and failure modes | "Analyze error handling strategies, recovery routines, and failure modes." |
191
215
  | `conventions` | Record coding styles, house rules, and constraints | "Document project-specific coding standards and house rules." |
192
216
 
193
- ### Why Sampling, Shuffling, and Limits Matter
217
+ ### Why Selection Still Matters
218
+
219
+ Everything runs at once now, so ordering no longer decides what gets asked -
220
+ but a budget still does.
194
221
 
195
- 1. **Balanced Graph Coverage**: If a warmup suite contains 20+ queries, running
196
- them sequentially with a limit (e.g. `--limit 4`) would only execute the
197
- first category (`architecture`), leaving workflows and integrations
198
- completely cold.
199
- 2. **`--shuffle`**: Randomizes the query sequence before limits are applied,
200
- ensuring that a limited budget run draws an even cross-section across
201
- different categories.
202
- 3. **`--category <name>`**: Enables focused warmup on a single subsystem (e.g.
203
- warming up only `integrations` after adding new API capabilities).
204
- 4. **`--limit <n>`**: Prevents unintended token expenditure by capping the total
205
- number of queries executed during test or verification passes.
222
+ 1. **Balanced coverage.** A 20-query suite cut to the first four with a limit
223
+ would warm `architecture` and leave `workflows` and `integrations` cold.
224
+ Shuffle before limiting so a partial budget draws a cross-section.
225
+ 2. **`--category <name>`** re-warms one subsystem after it changes - the
226
+ `integrations` queries alone once a new API is wired up - without paying for
227
+ the rest.
228
+ 3. **`--limit <n>`** caps what a verification pass costs. With sixteen in
229
+ flight the wall clock stops being the thing that limits you, so the cap has
230
+ to be deliberate.
231
+ 4. **`--dry-run`** prints the selected cases file and the exact command, writes
232
+ nothing, creates nothing, and bills nothing. Frontier models are expensive
233
+ enough that the plan is worth reading first.
206
234
 
207
- ### Dry Run Mode (`--dry-run`)
235
+ ---
236
+
237
+ ## Reading What Each Query Learned
238
+
239
+ Because every item kept its own memory, a warmup is auditable per question -
240
+ which a sequential run into one graph never was.
241
+
242
+ ```sh
243
+ DIR=".tmp/warmup-$STAMP/batch"
244
+
245
+ # Which items worked, and what each was asked
246
+ jq -r '.batch_results[] | "\(.id)\t\(if .ok then "ok" else "FAILED" end)"' "$DIR/batch.json"
247
+
248
+ # What one query actually committed
249
+ zen memory stats --dir "$DIR/architecture-layout/memory"
250
+ zen memory ls --dir "$DIR/architecture-layout/memory"
208
251
 
209
- Warming up memory with frontier reasoning models (`--model`) can be resource
210
- intensive. A `--dry-run` flag is essential for:
252
+ # What one query answered, and what it cost
253
+ jq '.stopReason, .usage' "$DIR/architecture-layout/output.json"
254
+ ```
255
+
256
+ A query that taught the graph nothing useful is excluded by leaving its
257
+ directory out of the merge. Nothing else has to be re-run.
211
258
 
212
- - Verifying resolved staging paths and backup targets.
213
- - Inspecting which queries and categories were selected after filtering,
214
- shuffling, and limits.
215
- - Checking the exact `zen run ...` commands that would be executed.
216
- - Guaranteeing that zero files are written, zero directories are created, and
217
- zero model tokens are billed.
259
+ An item that committed nothing leaves no `memory/` at all, so
260
+ `<batch-dir>/*/memory` never names a directory `merge` would refuse.
218
261
 
219
262
  ---
220
263
 
221
264
  ## Generating the Warmup Script in `scripts/`
222
265
 
223
- A project should maintain a dedicated warmup script located in `scripts/`
224
- (e.g. `scripts/memory_warmup.sh` or `scripts/memory-warmup.sh`).
225
-
226
- The script should automate the entire pipeline:
227
-
228
- 1. Accept command-line flags:
229
- - `--dry-run`: Preview selected queries, categories, and commands without executing.
230
- - `--limit <n>`: Run at most $n$ queries.
231
- - `--shuffle`: Randomize query order before applying limits.
232
- - `--category <name>`: Filter queries to a specific category.
233
- - `--model <provider:model>`: Override model with a reasoning flagship.
234
- - `--jobs <n>`: Run up to $n$ queries at once, each into its own memory.
235
- - `--incremental`: Copy existing project memory into staging before warming up.
236
- - `-y`, `--yes`: Promote memory without interactive confirmation.
237
- 2. Create timestamped staging paths under `.tmp/` (e.g.
238
- `.tmp/warmup-$(date +%Y%m%d-%H%M%S)/`) and timestamped per-query workspaces.
239
- 3. Filter, shuffle, and sample from categorized query definitions.
240
- 4. If `--dry-run` is active, print the execution plan and exit 0 before touching disk.
241
- 5. Invoke `zen run` with `--memory`, `--workspace`, `--model`, and `--json`.
242
- 6. Parse each outcome with `jq`, halt if a turn fails, and report per-query execution time and total elapsed time since start.
243
- 7. Under `--jobs <n>`, give each run its own `memory-N` directory and fold them back with `zen memory merge memory-* --dir memory`.
244
- 8. Print graph statistics via `zen memory stats --dir ...` between queries to track graph growth, followed by a final validation.
245
- 9. Safely backup and replace the project's `memory/` directory with the warmed graph.
266
+ A project should keep its warmup under version control as two files:
246
267
 
247
- ### Recommended Script Template (`scripts/memory_warmup.sh`)
268
+ - `scripts/warmup-queries.json` - the curated seed queries, in the shape above.
269
+ - `scripts/memory_warmup.sh` - selection, one batch, merge, validate, promote.
270
+
271
+ The script should:
248
272
 
249
- When generating a warmup script for a project, use the following production-ready
250
- pattern:
273
+ 1. Accept `--category`, `--limit`, `--shuffle`, `--concurrency`, `--model`,
274
+ `--fresh`, `--dry-run` and `-y/--yes`.
275
+ 2. Select from the queries file and write the selection to a timestamped
276
+ staging directory under `.tmp/`.
277
+ 3. Stop before touching disk under `--dry-run`, having printed the plan.
278
+ 4. Run **one** `zen run batch`, letting it own the parallelism, the per-item
279
+ workspaces and the per-item memories.
280
+ 5. Refuse to promote anything if any item failed, naming the ids from
281
+ `batch.json` and leaving the staging tree for inspection.
282
+ 6. Merge the item memories, print `zen memory stats`, then back up and promote.
283
+
284
+ ### Recommended Script Template (`scripts/memory_warmup.sh`)
251
285
 
252
286
  ```bash
253
287
  #!/usr/bin/env bash
254
288
  #
255
- # scripts/memory_warmup.sh — Pre-populate project memory using curated seed queries.
289
+ # scripts/memory_warmup.sh — Warm this project's memory with one parallel batch.
256
290
  #
257
291
  # Usage:
258
292
  # ./scripts/memory_warmup.sh [options]
259
293
  #
260
294
  # Options:
261
- # --dry-run Preview selected queries and commands without executing
262
- # --limit <n> Maximum number of queries to run
263
- # --shuffle Randomize query order before applying limits
264
- # --category <name> Filter queries to a specific category
265
- # --model <ref> Override model (e.g. anthropic:claude-sonnet-4-5)
266
- # --jobs <n> Run up to n queries at once, one memory each (default 1)
267
- # --incremental Seed staging memory with current memory/ before runs
268
- # -y, --yes Assume yes for memory promotion
269
- # -h, --help Show this help
295
+ # --category <name> Only queries whose id starts with "<name>-"
296
+ # --limit <n> Run at most n queries
297
+ # --shuffle Randomize order before applying --limit
298
+ # --concurrency <n> How many at a time (default 4; zen allows up to 32)
299
+ # --model <ref> Warm up with a stronger model than the runtime one
300
+ # --fresh Start every item from an empty graph, not from memory/
301
+ # --dry-run Print the plan and exit; write nothing, bill nothing
302
+ # -y, --yes Promote without asking
303
+ # -h, --help Show this help
270
304
  #
271
305
  # Examples:
272
306
  # ./scripts/memory_warmup.sh --dry-run
273
- # ./scripts/memory_warmup.sh --dry-run --category workflows --limit 2
307
+ # ./scripts/memory_warmup.sh --category integrations
274
308
  # ./scripts/memory_warmup.sh --shuffle --limit 5 --model anthropic:claude-sonnet-4-5
275
- # ./scripts/memory_warmup.sh --jobs 4
276
- # ./scripts/memory_warmup.sh --incremental
309
+ # ./scripts/memory_warmup.sh --fresh -y
277
310
  #
278
311
 
279
312
  set -euo pipefail
280
313
 
281
- # Find project root regardless of invocation directory
282
314
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
283
315
  cd "$ROOT"
284
316
 
285
- # Default configuration
286
- MODEL="${ZEN_WARMUP_MODEL:-}"
287
- INCREMENTAL=false
288
- ASSUME_YES=false
289
- DRY_RUN=false
317
+ QUERIES="scripts/warmup-queries.json"
318
+ PROJECT_MEM="memory"
319
+
320
+ CATEGORY=""
290
321
  LIMIT=0
291
- JOBS=1
292
322
  SHUFFLE=false
293
- CATEGORY_FILTER=""
294
-
295
- # Helper to format seconds as human-readable duration
296
- format_duration() {
297
- local total_sec=$1
298
- local hours=$((total_sec / 3600))
299
- local mins=$(((total_sec % 3600) / 60))
300
- local secs=$((total_sec % 60))
301
- if [ $hours -gt 0 ]; then
302
- echo "${hours}h ${mins}m ${secs}s"
303
- elif [ $mins -gt 0 ]; then
304
- echo "${mins}m ${secs}s"
305
- else
306
- echo "${secs}s"
307
- fi
323
+ CONCURRENCY=4
324
+ MODEL="${ZEN_WARMUP_MODEL:-}"
325
+ FRESH=false
326
+ DRY_RUN=false
327
+ ASSUME_YES=false
328
+
329
+ usage() {
330
+ sed -n '2,25p' "$0" | sed 's/^# \{0,1\}//'
308
331
  }
309
332
 
310
- # Parse arguments
311
333
  while [[ $# -gt 0 ]]; do
312
334
  case "$1" in
313
- --dry-run)
314
- DRY_RUN=true
315
- shift
316
- ;;
317
- --limit)
318
- LIMIT="$2"
319
- shift 2
320
- ;;
321
- --shuffle)
322
- SHUFFLE=true
323
- shift
324
- ;;
325
- --category)
326
- CATEGORY_FILTER="$2"
327
- shift 2
328
- ;;
329
- --model)
330
- MODEL="$2"
331
- shift 2
332
- ;;
333
- --jobs)
334
- JOBS="$2"
335
- shift 2
336
- ;;
337
- --incremental)
338
- INCREMENTAL=true
339
- shift
340
- ;;
341
- -y|--yes)
342
- ASSUME_YES=true
343
- shift
344
- ;;
345
- -h|--help)
346
- echo "Usage: ./scripts/memory_warmup.sh [options]"
347
- echo ""
348
- echo "Options:"
349
- echo " --dry-run Preview selected queries and commands without executing"
350
- echo " --limit <n> Maximum number of queries to run"
351
- echo " --shuffle Randomize query order before applying limits"
352
- echo " --category <name> Filter queries to a specific category"
353
- echo " --model <ref> Override model (e.g. anthropic:claude-sonnet-4-5)"
354
- echo " --jobs <n> Run up to n queries at once, one memory each (default 1)"
355
- echo " --incremental Seed staging memory with current memory/ before runs"
356
- echo " -y, --yes Assume yes for memory promotion"
357
- echo " -h, --help Show this help"
358
- exit 0
359
- ;;
360
- *)
361
- echo "Error: Unknown option $1" >&2
362
- exit 2
363
- ;;
335
+ --category) CATEGORY="$2"; shift 2 ;;
336
+ --limit) LIMIT="$2"; shift 2 ;;
337
+ --shuffle) SHUFFLE=true; shift ;;
338
+ --concurrency) CONCURRENCY="$2"; shift 2 ;;
339
+ --model) MODEL="$2"; shift 2 ;;
340
+ --fresh) FRESH=true; shift ;;
341
+ --dry-run) DRY_RUN=true; shift ;;
342
+ -y|--yes) ASSUME_YES=true; shift ;;
343
+ -h|--help) usage; exit 0 ;;
344
+ *) echo "Error: unknown option $1" >&2; exit 2 ;;
364
345
  esac
365
346
  done
366
347
 
367
- STAMP="$(date +%Y%m%d-%H%M%S)"
368
- SCRATCH_DIR=".tmp/warmup-$STAMP"
369
- STAGING_MEM="$SCRATCH_DIR/memory"
370
- WORKSPACES_DIR="$SCRATCH_DIR/workspaces"
371
- PROJECT_MEM="memory"
372
-
373
- # Define curated seed queries structured by category ("category|prompt")
374
- ALL_QUERIES=(
375
- "architecture|Investigate the project architecture and summarize the main components and data flow."
376
- "architecture|Analyze module boundaries, dependency graphs, and core interfaces."
377
- "workflows|Formulate standard operational plans for triaging and resolving common domain tasks."
378
- "workflows|Trace the end-to-end user request lifecycle and hand-off points."
379
- "integrations|Identify external services, APIs, and key credential dependencies."
380
- "integrations|Verify database and storage schemas, access patterns, and persistence rules."
381
- "errors|Analyze error handling strategies, recovery routines, and failure modes."
382
- "errors|Identify retry policies, rate limits, and fallback behaviors."
383
- "conventions|Document project-specific coding standards, house rules, and commit policies."
384
- )
385
-
386
- # 1. Filter by category if requested
387
- FILTERED=()
388
- for item in "${ALL_QUERIES[@]}"; do
389
- cat="${item%%|*}"
390
- if [ -z "$CATEGORY_FILTER" ] || [ "$cat" = "$CATEGORY_FILTER" ]; then
391
- FILTERED+=("$item")
392
- fi
393
- done
348
+ command -v jq >/dev/null 2>&1 || { echo "Error: jq is required." >&2; exit 1; }
349
+ [ -f "$QUERIES" ] || { echo "Error: $QUERIES not found." >&2; exit 1; }
394
350
 
395
- if [ ${#FILTERED[@]} -eq 0 ]; then
396
- echo "Error: No queries matched category '$CATEGORY_FILTER'." >&2
397
- exit 1
398
- fi
351
+ # --- Select -----------------------------------------------------------------
352
+ # One compact JSON object per line, so ordering and limiting are line work.
353
+ SELECTED="$(jq -c --arg cat "$CATEGORY" \
354
+ '.batch[] | select($cat == "" or (.id // "" | startswith($cat + "-")))' \
355
+ "$QUERIES")"
399
356
 
400
- # 2. Shuffle if requested
401
357
  if [ "$SHUFFLE" = true ]; then
402
- TEMP=()
403
- if command -v python3 >/dev/null 2>&1; then
404
- while IFS= read -r line; do
405
- [ -n "$line" ] && TEMP+=("$line")
406
- done < <(printf '%s\n' "${FILTERED[@]}" | python3 -c 'import sys, random; lines = [l for l in sys.stdin.read().splitlines() if l]; random.shuffle(lines); print("\n".join(lines))')
407
- elif command -v shuf >/dev/null 2>&1; then
408
- while IFS= read -r line; do
409
- [ -n "$line" ] && TEMP+=("$line")
410
- done < <(printf '%s\n' "${FILTERED[@]}" | shuf)
411
- elif sort -R </dev/null >/dev/null 2>&1; then
412
- while IFS= read -r line; do
413
- [ -n "$line" ] && TEMP+=("$line")
414
- done < <(printf '%s\n' "${FILTERED[@]}" | sort -R)
358
+ if command -v shuf >/dev/null 2>&1; then
359
+ SELECTED="$(printf '%s\n' "$SELECTED" | shuf)"
415
360
  else
416
- TEMP=("${FILTERED[@]}")
361
+ SELECTED="$(printf '%s\n' "$SELECTED" | sort -R)"
417
362
  fi
418
- FILTERED=("${TEMP[@]}")
419
363
  fi
420
364
 
421
- # 3. Apply limit if requested
422
- SELECTED=()
423
- if [ "$LIMIT" -gt 0 ] && [ "$LIMIT" -lt "${#FILTERED[@]}" ]; then
424
- for ((i = 0; i < LIMIT; i++)); do
425
- SELECTED+=("${FILTERED[i]}")
426
- done
427
- else
428
- SELECTED=("${FILTERED[@]}")
365
+ # awk rather than head: head closes the pipe early, and a SIGPIPE inside a
366
+ # command substitution takes the whole script down under `set -e`.
367
+ if [ "$LIMIT" -gt 0 ]; then
368
+ SELECTED="$(printf '%s\n' "$SELECTED" | awk -v n="$LIMIT" 'NR <= n')"
369
+ fi
370
+
371
+ COUNT="$(printf '%s\n' "$SELECTED" | awk 'NF { n++ } END { print n + 0 }')"
372
+ if [ "$COUNT" -eq 0 ]; then
373
+ echo "Error: no queries matched${CATEGORY:+ category '$CATEGORY'}." >&2
374
+ exit 1
375
+ fi
376
+ CASES="$(printf '%s\n' "$SELECTED" | jq -s '{ batch: . }')"
377
+
378
+ STAMP="$(date +%Y%m%d-%H%M%S)"
379
+ STAGE=".tmp/warmup-$STAMP"
380
+ BATCH_DIR="$STAGE/batch"
381
+ STAGING_MEM="$STAGE/memory"
382
+ CASES_FILE="$STAGE/cases.json"
383
+
384
+ CMD=(zen run batch --input "$CASES_FILE" --batch-dir "$BATCH_DIR"
385
+ --concurrency "$CONCURRENCY")
386
+ if [ -n "$MODEL" ]; then
387
+ CMD+=(--model "$MODEL")
388
+ fi
389
+ if [ "$FRESH" = true ]; then
390
+ # A source that does not exist yet: every item starts from an empty graph
391
+ # instead of a copy of what the project already knows.
392
+ CMD+=(--memory "$STAGE/from-nothing")
429
393
  fi
430
394
 
431
- # 4. Handle Dry Run mode
395
+ # --- Dry run ----------------------------------------------------------------
432
396
  if [ "$DRY_RUN" = true ]; then
433
- echo "=== DRY RUN: Previewing Warmup Plan ==="
434
- echo "Project root: $ROOT"
435
- echo "Staging memory: $STAGING_MEM"
436
- echo "Target memory: $PROJECT_MEM"
437
- echo "Model override: ${MODEL:-<default>}"
438
- echo "Category filter: ${CATEGORY_FILTER:-<all>}"
439
- echo "Shuffled: $SHUFFLE"
440
- echo "Limit: ${LIMIT:-none}"
441
- echo "Jobs: $JOBS"
442
- echo "Selected queries: ${#SELECTED[@]} of ${#ALL_QUERIES[@]}"
397
+ echo "=== DRY RUN: warmup plan ==="
398
+ echo "Project root: $ROOT"
399
+ echo "Staging: $STAGE"
400
+ echo "Target memory: $PROJECT_MEM"
401
+ echo "Model: ${MODEL:-<project default>}"
402
+ echo "Category: ${CATEGORY:-<all>}"
403
+ echo "Shuffled: $SHUFFLE"
404
+ echo "Limit: $([ "$LIMIT" -gt 0 ] && echo "$LIMIT" || echo none)"
405
+ echo "Concurrency: $CONCURRENCY"
406
+ echo "Starting from: $([ "$FRESH" = true ] && echo "an empty graph" || echo "a copy of $PROJECT_MEM")"
407
+ echo "Queries: $COUNT"
443
408
  echo ""
444
- echo "Planned queries:"
445
- for item in "${SELECTED[@]}"; do
446
- cat="${item%%|*}"
447
- q="${item#*|}"
448
- printf ' [%-14s] %s\n' "$cat" "$q"
449
- done
409
+ printf '%s\n' "$CASES" | jq -r '.batch[] | " [\(.id)] \(.input)"'
450
410
  echo ""
451
- echo "Sample command that would execute:"
452
- SAMPLE_Q="${SELECTED[0]#*|}"
453
- if [ "$JOBS" -le 1 ]; then
454
- SAMPLE_MEM="$STAGING_MEM"
455
- else
456
- SAMPLE_MEM="$SCRATCH_DIR/memory-1"
457
- fi
458
- echo " zen run --memory \"$SAMPLE_MEM\" --workspace \"$WORKSPACES_DIR/run-1-<timestamp>\" ${MODEL:+--model \"$MODEL\" }--json \"$SAMPLE_Q\""
459
- if [ "$JOBS" -gt 1 ]; then
460
- echo " zen memory merge \"$SCRATCH_DIR\"/memory-* --dir \"$STAGING_MEM\" --yes"
461
- fi
462
- echo " zen memory stats --dir \"$STAGING_MEM\""
411
+ echo "Would run:"
412
+ printf ' '; printf '%q ' "${CMD[@]}"; echo ""
413
+ echo " zen memory merge $BATCH_DIR/*/memory --dir $STAGING_MEM --yes"
414
+ echo " zen memory stats --dir $STAGING_MEM"
463
415
  echo ""
464
- echo "Post-run validation that would run:"
465
- echo " zen memory stats --dir \"$STAGING_MEM\""
466
- echo ""
467
- echo "Dry run complete. No files created, no commands executed."
416
+ echo "Dry run complete. Nothing created, nothing billed."
468
417
  exit 0
469
418
  fi
470
419
 
471
- WARMUP_START=$(date +%s)
420
+ # --- Run --------------------------------------------------------------------
421
+ mkdir -p "$STAGE"
422
+ printf '%s\n' "$CASES" >"$CASES_FILE"
472
423
 
473
- echo "=== Zenera Neo Memory Warmup ==="
474
- echo "Project root: $ROOT"
475
- echo "Staging directory: $SCRATCH_DIR"
476
- if [ -n "$MODEL" ]; then
477
- echo "Model override: $MODEL"
478
- fi
479
- echo "Selected queries: ${#SELECTED[@]}"
424
+ echo "=== Zenera Neo memory warmup ==="
425
+ echo "Staging: $STAGE"
426
+ echo "Queries: $COUNT, $CONCURRENCY at a time"
480
427
  echo ""
481
428
 
482
- # Cleanup scratch on trap unless preserved for inspection on failure
483
- cleanup() {
484
- local rc=$?
485
- if [ $rc -ne 0 ]; then
486
- echo "" >&2
487
- echo "Warmup FAILED (exit code $rc)." >&2
488
- echo "Staging files preserved for inspection at: $SCRATCH_DIR" >&2
489
- fi
490
- }
491
- trap cleanup EXIT
492
-
493
- # Prepare staging paths
494
- mkdir -p "$WORKSPACES_DIR"
495
-
496
- seed_memory() {
497
- mkdir -p "$1"
498
- if [ "$INCREMENTAL" = true ] && [ -d "$PROJECT_MEM" ]; then
499
- cp -R "$PROJECT_MEM/." "$1/"
500
- fi
501
- }
502
-
503
- # One query, into the memory directory it is handed. The lock is per directory,
504
- # so this is the unit that more than one of can be in flight at a time.
505
- run_query() {
506
- local index="$1" category="$2" query="$3" mem="$4"
507
- local workspace="$WORKSPACES_DIR/run-$index-$(date +%s)"
508
- mkdir -p "$workspace"
509
-
510
- local cmd=(zen run --memory "$mem" --workspace "$workspace" --json)
511
- if [ -n "$MODEL" ]; then
512
- cmd+=(--model "$MODEL")
513
- fi
514
- cmd+=("$query")
515
-
516
- echo "[$index/${#SELECTED[@]}] [$category] \"$query\""
517
- echo " -> Workspace: $workspace"
518
-
519
- local start out
520
- start=$(date +%s)
521
- if ! out="$("${cmd[@]}")"; then
522
- echo " -> Command failed" >&2
523
- return 1
524
- fi
525
-
526
- if command -v jq >/dev/null 2>&1; then
527
- local stop agent
528
- stop=$(echo "$out" | jq -r '.stopReason // empty')
529
- agent=$(echo "$out" | jq -r '.agent // empty')
530
- echo " -> Completed: agent=$agent stopReason=$stop"
531
- if [ "$stop" != "final" ]; then
532
- echo " -> Unexpected stop reason: $stop" >&2
533
- return 1
534
- fi
535
- else
536
- echo " -> Turn completed successfully."
537
- fi
538
-
539
- local now
540
- now=$(date +%s)
541
- echo " -> Timing: query took $(format_duration $((now - start))) | elapsed since start: $(format_duration $((now - WARMUP_START)))"
542
- }
543
-
544
- # `wait` with no arguments always reports success, so every child is waited for
545
- # by pid. Batches of $JOBS rather than a rolling queue: `wait -n` wants bash
546
- # 4.3 and macOS still ships 3.2.
547
- PIDS=()
548
- FAILED=false
549
- drain() {
550
- if [ ${#PIDS[@]} -eq 0 ]; then
551
- return 0
552
- fi
553
- local pid
554
- for pid in "${PIDS[@]}"; do
555
- wait "$pid" || FAILED=true
556
- done
557
- PIDS=()
558
- }
559
-
560
- echo "Running ${#SELECTED[@]} warmup queries (jobs: $JOBS)..."
561
- echo "----------------------------------------"
562
-
563
- if [ "$JOBS" -le 1 ]; then
564
- seed_memory "$STAGING_MEM"
565
- COUNT=0
566
- for item in "${SELECTED[@]}"; do
567
- COUNT=$((COUNT + 1))
568
- run_query "$COUNT" "${item%%|*}" "${item#*|}" "$STAGING_MEM"
569
-
570
- # Print memory stats between queries to track graph growth
571
- if [ -f "$STAGING_MEM/manifest.json" ]; then
572
- echo ""
573
- echo "Memory stats after query $COUNT:"
574
- zen memory stats --dir "$STAGING_MEM"
575
- else
576
- echo " -> Memory stats: No memories committed yet."
577
- fi
578
- echo ""
579
- done
580
- else
581
- SHARDS=()
582
- COUNT=0
583
- for item in "${SELECTED[@]}"; do
584
- COUNT=$((COUNT + 1))
585
- SHARD="$SCRATCH_DIR/memory-$COUNT"
586
- seed_memory "$SHARD"
587
- SHARDS+=("$SHARD")
588
- run_query "$COUNT" "${item%%|*}" "${item#*|}" "$SHARD" \
589
- >"$SCRATCH_DIR/run-$COUNT.log" 2>&1 &
590
- PIDS+=($!)
591
- if [ ${#PIDS[@]} -ge "$JOBS" ]; then
592
- drain
593
- fi
594
- done
595
- drain
596
-
597
- for log in "$SCRATCH_DIR"/run-*.log; do
598
- if [ -f "$log" ]; then
599
- cat "$log"
600
- echo ""
601
- fi
602
- done
603
-
604
- if [ "$FAILED" = true ]; then
605
- echo "One or more warmup runs failed; nothing was promoted." >&2
606
- exit 1
607
- fi
429
+ # The batch prints its directory on stdout; we chose it, so we do not need it
430
+ # repeated. Progress and per-item results are on stderr and stay visible.
431
+ if ! "${CMD[@]}" >/dev/null; then
432
+ echo "" >&2
433
+ echo "Some queries failed; nothing was promoted." >&2
434
+ jq -r '.batch_results[] | select(.ok | not) | " \(.id): \(.error.message)"' \
435
+ "$BATCH_DIR/batch.json" >&2
436
+ echo "Staging left for inspection at: $STAGE" >&2
437
+ exit 1
438
+ fi
608
439
 
609
- # Only a memory something was committed to can be merged; a run that found
610
- # nothing worth keeping never writes a manifest.
611
- MERGE=()
612
- for shard in "${SHARDS[@]}"; do
613
- if [ -f "$shard/manifest.json" ]; then
614
- MERGE+=("$shard")
615
- fi
616
- done
617
- if [ ${#MERGE[@]} -eq 0 ]; then
618
- echo "No warmup run committed anything." >&2
619
- exit 1
620
- fi
440
+ # --- Merge ------------------------------------------------------------------
441
+ # An item that committed nothing leaves no memory behind, so this glob only
442
+ # ever names real graphs.
443
+ MERGE=()
444
+ for mem in "$BATCH_DIR"/*/memory; do
445
+ [ -d "$mem" ] && MERGE+=("$mem")
446
+ done
621
447
 
622
- echo "Merging ${#MERGE[@]} memories into $STAGING_MEM..."
623
- zen memory merge "${MERGE[@]}" --dir "$STAGING_MEM" --yes
624
- echo ""
448
+ if [ ${#MERGE[@]} -eq 0 ]; then
449
+ echo "No query committed anything to memory; nothing to promote." >&2
450
+ echo "Answers are still at: $BATCH_DIR" >&2
451
+ exit 1
625
452
  fi
626
453
 
627
- echo "----------------------------------------"
628
- TOTAL_RUN_TIME=$(( $(date +%s) - WARMUP_START ))
629
- echo "All warmup queries finished successfully in $(format_duration $TOTAL_RUN_TIME)."
630
454
  echo ""
455
+ echo "Merging ${#MERGE[@]} memories into $STAGING_MEM..."
456
+ zen memory merge "${MERGE[@]}" --dir "$STAGING_MEM" --yes
631
457
 
632
- # Validate staging memory
633
- echo "Validating staging memory graph:"
458
+ echo ""
459
+ echo "Warmed graph:"
634
460
  zen memory stats --dir "$STAGING_MEM"
635
461
  echo ""
636
462
 
637
- # Confirmation and promotion
463
+ # --- Promote ----------------------------------------------------------------
638
464
  if [ "$ASSUME_YES" = false ]; then
639
- read -r -p "Promote staging memory to '$PROJECT_MEM'? [y/N] " CONFIRM
465
+ read -r -p "Promote this graph to '$PROJECT_MEM'? [y/N] " CONFIRM
640
466
  if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
641
- echo "Warmup cancelled. Staging files left in $SCRATCH_DIR."
467
+ echo "Not promoted. Staging left at $STAGE."
642
468
  exit 0
643
469
  fi
644
470
  fi
645
471
 
646
- # Backup existing project memory if present
647
472
  if [ -d "$PROJECT_MEM" ]; then
648
- BACKUP_MEM="memory.bak.$(date +%Y%m%d-%H%M%S)"
649
- echo "Backing up current memory to $BACKUP_MEM..."
650
- mv "$PROJECT_MEM" "$BACKUP_MEM"
473
+ BACKUP="memory.bak.$STAMP"
474
+ echo "Backing up current memory to $BACKUP..."
475
+ mv "$PROJECT_MEM" "$BACKUP"
651
476
  fi
652
477
 
653
- # Promote staging memory to project root
654
- echo "Promoting warmed memory to $PROJECT_MEM..."
655
478
  mv "$STAGING_MEM" "$PROJECT_MEM"
479
+ rm -rf "$STAGE"
656
480
 
657
- # Clean up remaining scratch workspaces
658
- rm -rf "$SCRATCH_DIR"
659
-
660
- echo "Warmup complete! Project memory is ready."
481
+ echo "Warmup complete. Project memory is ready."
661
482
  ```
662
483
 
663
484
  ---
664
485
 
486
+ ## When Not to Batch
487
+
488
+ A batch gives every item its own graph, so no item can recall what another one
489
+ just learned. When the queries genuinely build on each other - a later question
490
+ that only makes sense once an earlier one has committed its answer - run those
491
+ few in sequence with plain `zen run --memory`, and batch the rest.
492
+
493
+ `merge` gives back everything except that cross-query recall. Reach for the
494
+ sequence only where the recall is the point; it costs the wall clock that
495
+ warming in parallel is for.
496
+
497
+ ---
498
+
665
499
  ## Best Practices for Seed Queries
666
500
 
667
- When authoring or generating queries for the warmup script:
668
-
669
- 1. **Group by Category**:
670
- - Organize queries into clear domain categories (`architecture`, `workflows`,
671
- `integrations`, `errors`, `conventions`) so test runs can be sampled or
672
- targeted easily.
673
- 2. **Use `--dry-run` First**:
674
- - Always verify candidate queries, model overrides, and category distribution
675
- with `--dry-run` before initiating a live warmup.
676
- 3. **Prompts That Induce Plan Formation**:
677
- - Instruct the agent to "analyze and determine the recommended pattern for..." so
678
- it stores durable `plan` and `fact` nodes rather than transient output.
679
- 4. **Verify Node Vectorisation**:
680
- - Confirm in `zen memory stats --dir ...` that vector count equals node count
681
- (ensuring your embedding model is configured and active).
682
- 5. **Keep Seed Queries Under Version Control**:
683
- - Store the seed prompts inside the script or an adjacent `scripts/warmup-queries.txt`
684
- so warmup is reproducible across environments and model upgrades.
501
+ 1. **Name ids by category.** `architecture-layout`, `workflows-triage`. The id
502
+ is the directory name, the selector, and the label in every report.
503
+ 2. **Dry-run first.** Verify the selection, the model and the source graph
504
+ before spending a flagship model on twenty questions at once.
505
+ 3. **Write prompts that induce plan formation.** "Analyze and determine the
506
+ recommended pattern for..." stores durable `plan` and `fact` nodes; "list
507
+ the files in src/" stores nothing worth keeping.
508
+ 4. **Verify vectorisation.** In `zen memory stats`, the vector count should
509
+ equal the node count. If it does not, the embedding model is not configured
510
+ and recall will fall back to text alone.
511
+ 5. **Keep the queries under version control.** `scripts/warmup-queries.json` is
512
+ the record of what the project's memory was built from, and the thing to
513
+ re-run after a model upgrade.
514
+ 6. **Warm in one batch, not many.** A second batch cannot see what the first
515
+ learned unless it has been merged and promoted first - so either merge in
516
+ between, or ask everything at once.