@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.
- package/README.md +35 -2
- package/dist/ask.d.ts +51 -0
- package/dist/ask.js +143 -0
- package/dist/batch.d.ts +77 -0
- package/dist/batch.js +338 -0
- package/dist/command.d.ts +6 -0
- package/dist/commands/inspect.d.ts +11 -0
- package/dist/commands/inspect.js +388 -89
- package/dist/commands/list.js +128 -19
- package/dist/commands/memory.js +342 -12
- package/dist/commands/models.js +14 -2
- package/dist/commands/run.js +139 -34
- package/dist/commands/sandbox.js +72 -1
- package/dist/engine.d.ts +19 -0
- package/dist/engine.js +60 -8
- package/dist/liveness.js +6 -3
- package/dist/main.js +5 -2
- package/dist/podman.d.ts +16 -0
- package/dist/podman.js +115 -2
- package/dist/progress.d.ts +33 -0
- package/dist/progress.js +402 -0
- package/dist/request.d.ts +45 -0
- package/dist/request.js +300 -0
- package/dist/sandbox.js +10 -0
- package/dist/session.d.ts +26 -1
- package/dist/session.js +42 -3
- package/dist/term.d.ts +3 -0
- package/dist/term.js +17 -1
- package/dist/trace.d.ts +84 -0
- package/dist/trace.js +686 -0
- package/dist/validate.js +25 -3
- package/package.json +2 -2
- package/templates/editor/.github/copilot-instructions.md +11 -6
- package/templates/editor/.github/prompts/finetune.prompt.md +251 -0
- package/templates/editor/.github/skills/zen-cli/SKILL.md +9 -3
- package/templates/editor/.github/skills/zen-cli/references/check.md +7 -5
- package/templates/editor/.github/skills/zen-cli/references/inspect.md +262 -21
- package/templates/editor/.github/skills/zen-cli/references/memory.md +89 -19
- package/templates/editor/.github/skills/zen-cli/references/models.md +1 -1
- package/templates/editor/.github/skills/zen-cli/references/projects.md +12 -5
- package/templates/editor/.github/skills/zen-cli/references/run.md +201 -4
- package/templates/editor/.github/skills/zen-finetune/SKILL.md +1000 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/collect.sh +432 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/sample.sh +199 -0
- package/templates/editor/.github/skills/zen-inspect/SKILL.md +396 -0
- package/templates/editor/.github/skills/zen-memory/SKILL.md +74 -29
- package/templates/editor/.github/skills/zen-memory-warmup/SKILL.md +363 -531
- package/templates/editor/.github/skills/zen-sandbox/SKILL.md +19 -0
- package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +334 -0
- package/templates/editor/.github/skills/zen-sandbox-capacity/references/loadtest.md +181 -0
- package/templates/editor/.github/skills/zen-sandbox-capacity/scripts/preflight_sandbox.sh +457 -0
- package/templates/editor/.vscode/agents.schema.json +1 -1
- package/templates/editor/.vscode/settings.json +1 -0
- package/templates/project/agents/files-instructions.md +13 -0
- package/templates/project/agents/fork-instructions.md +27 -0
- package/templates/project/agents/memory-instructions.md +80 -7
- 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
|
|
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
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
2. **
|
|
25
|
-
a
|
|
26
|
-
model.
|
|
27
|
-
|
|
28
|
-
3. **Consistency
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
##
|
|
37
|
+
## What the Command Already Does
|
|
35
38
|
|
|
36
|
-
Warmup
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
to
|
|
42
|
-
|
|
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
|
-
|
|
46
|
-
STAMP="$(date +%Y%m%d-%H%M%S)"
|
|
47
|
-
--memory ".tmp/warmup-$STAMP/memory"
|
|
48
|
-
```
|
|
58
|
+
---
|
|
49
59
|
|
|
50
|
-
|
|
60
|
+
## Core Safety Rules
|
|
51
61
|
|
|
52
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
--
|
|
78
|
+
DIR="$(zen run batch --input cases.json)"
|
|
81
79
|
```
|
|
82
80
|
|
|
83
|
-
|
|
84
|
-
utilized by the runtime model.
|
|
81
|
+
### 2. Choose the graph every item starts from
|
|
85
82
|
|
|
86
|
-
|
|
83
|
+
`--memory <dir>` names the source. There are two warmups, and they differ only
|
|
84
|
+
in what they point it at:
|
|
87
85
|
|
|
88
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
112
|
-
.tmp/warmup-$STAMP/memory-2
|
|
113
|
-
...
|
|
107
|
+
--model anthropic:claude-sonnet-4-5
|
|
114
108
|
```
|
|
115
109
|
|
|
116
|
-
|
|
117
|
-
offline, contacts no model, and preserves node ids:
|
|
110
|
+
It applies to every item in the batch.
|
|
118
111
|
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
##
|
|
127
|
+
## The Shape of a Warmup
|
|
132
128
|
|
|
133
129
|
```mermaid
|
|
134
130
|
flowchart LR
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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["
|
|
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
|
|
161
|
-
B --> C
|
|
162
|
-
C --> D["
|
|
163
|
-
|
|
164
|
-
E
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
181
|
-
|
|
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
|
|
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
|
|
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
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
4. **`--
|
|
205
|
-
|
|
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
|
-
|
|
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
|
-
|
|
210
|
-
|
|
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
|
-
|
|
213
|
-
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
250
|
-
|
|
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 —
|
|
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
|
-
# --
|
|
262
|
-
# --limit <n>
|
|
263
|
-
# --shuffle
|
|
264
|
-
# --
|
|
265
|
-
# --model <ref>
|
|
266
|
-
# --
|
|
267
|
-
# --
|
|
268
|
-
# -y, --yes
|
|
269
|
-
# -h, --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 --
|
|
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 --
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
--
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
--
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
|
|
368
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
|
|
403
|
-
|
|
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
|
-
|
|
361
|
+
SELECTED="$(printf '%s\n' "$SELECTED" | sort -R)"
|
|
417
362
|
fi
|
|
418
|
-
FILTERED=("${TEMP[@]}")
|
|
419
363
|
fi
|
|
420
364
|
|
|
421
|
-
#
|
|
422
|
-
|
|
423
|
-
if [ "$LIMIT" -gt 0 ]
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
-
#
|
|
395
|
+
# --- Dry run ----------------------------------------------------------------
|
|
432
396
|
if [ "$DRY_RUN" = true ]; then
|
|
433
|
-
echo "=== DRY RUN:
|
|
434
|
-
echo "Project root:
|
|
435
|
-
echo "Staging
|
|
436
|
-
echo "Target memory:
|
|
437
|
-
echo "Model
|
|
438
|
-
echo "Category
|
|
439
|
-
echo "Shuffled:
|
|
440
|
-
echo "Limit:
|
|
441
|
-
echo "
|
|
442
|
-
echo "
|
|
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
|
-
|
|
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 "
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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 "
|
|
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
|
-
|
|
420
|
+
# --- Run --------------------------------------------------------------------
|
|
421
|
+
mkdir -p "$STAGE"
|
|
422
|
+
printf '%s\n' "$CASES" >"$CASES_FILE"
|
|
472
423
|
|
|
473
|
-
echo "=== Zenera Neo
|
|
474
|
-
echo "
|
|
475
|
-
echo "
|
|
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
|
-
#
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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
|
-
|
|
623
|
-
|
|
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
|
-
|
|
633
|
-
echo "
|
|
458
|
+
echo ""
|
|
459
|
+
echo "Warmed graph:"
|
|
634
460
|
zen memory stats --dir "$STAGING_MEM"
|
|
635
461
|
echo ""
|
|
636
462
|
|
|
637
|
-
#
|
|
463
|
+
# --- Promote ----------------------------------------------------------------
|
|
638
464
|
if [ "$ASSUME_YES" = false ]; then
|
|
639
|
-
read -r -p "Promote
|
|
465
|
+
read -r -p "Promote this graph to '$PROJECT_MEM'? [y/N] " CONFIRM
|
|
640
466
|
if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
|
|
641
|
-
echo "
|
|
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
|
-
|
|
649
|
-
echo "Backing up current memory to $
|
|
650
|
-
mv "$PROJECT_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
|
-
|
|
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
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
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.
|