@zenera/cli 1.1.19 → 1.1.21
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 +6 -0
- package/dist/banner.d.ts +20 -0
- package/dist/banner.js +10 -0
- package/dist/commands/cache.js +1 -0
- package/dist/commands/check.js +34 -2
- package/dist/commands/index.js +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/meta.js +1 -1
- package/dist/commands/models.js +1 -0
- package/dist/commands/open.js +1 -1
- package/dist/lib.d.ts +1 -1
- package/dist/lib.js +1 -1
- package/dist/main.js +8 -1
- package/dist/meta.js +19 -7
- package/dist/scaffold.d.ts +37 -0
- package/dist/scaffold.js +67 -3
- package/dist/term.d.ts +2 -0
- package/dist/term.js +25 -0
- package/dist/tui/app.js +237 -65
- package/dist/tui/theme.d.ts +10 -0
- package/dist/tui/theme.js +4 -0
- package/dist/tui/wrap.d.ts +100 -18
- package/dist/tui/wrap.js +500 -27
- package/dist/validate.js +115 -9
- package/package.json +2 -2
- package/templates/editor/.github/copilot-instructions.md +33 -8
- package/templates/editor/.github/skills/zen-cli/references/projects.md +19 -8
- package/templates/editor/.github/skills/zen-memory/SKILL.md +77 -33
- package/templates/editor/.github/skills/zen-memory/scripts/check-instructions.sh +2 -1
- package/templates/editor/.vscode/agents.schema.json +611 -0
- package/templates/editor/.vscode/extensions.json +3 -0
- package/templates/editor/.vscode/settings.json +8 -0
- package/templates/project/agents/fork-instructions.md +151 -0
- package/templates/project/agents/memory-instructions.md +120 -8
package/dist/validate.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { EXA_GROUP, FileSkillProvider, SANDBOX_MOUNT, Sandbox, exaTools, projectRegistry, readProjectConfig, sandboxTools, selectTools, workspaceTools, } from '@zenera/neo';
|
|
2
|
-
import { existsSync, mkdtempSync, readdirSync, realpathSync, rmSync, statSync } from 'node:fs';
|
|
1
|
+
import { EXA_GROUP, FileSkillProvider, SANDBOX_MOUNT, Sandbox, exaTools, frontmatter, projectRegistry, readProjectConfig, sandboxTools, selectTools, toList, workspaceTools, } from '@zenera/neo';
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
5
5
|
import { auditModels, credentialFor } from "./audit.js";
|
|
@@ -7,6 +7,7 @@ import { resolveBuild } from "./image.js";
|
|
|
7
7
|
import { SHAPES, envNames, form } from "./keys.js";
|
|
8
8
|
import { probeModels } from "./liveness.js";
|
|
9
9
|
import { BuildError, ensurePodmanReady } from "./podman.js";
|
|
10
|
+
import { staleShared } from "./scaffold.js";
|
|
10
11
|
// Mirrors the loader's own constants (`packages/neo/src/project/load.ts`).
|
|
11
12
|
// Duplicated rather than exported, because a check that agreed with the loader
|
|
12
13
|
// by construction could not report that the two had diverged.
|
|
@@ -14,23 +15,39 @@ const CONFIG_NAMES = ['agents.yaml', 'agents.yml', 'agents/agents.yaml', 'agents
|
|
|
14
15
|
const AGENTS_DIR = 'agents';
|
|
15
16
|
const HOUSE_RULES = 'agents/instructions.md';
|
|
16
17
|
const INSTRUCTIONS_SUFFIX = '-instructions.md';
|
|
18
|
+
/** The only place memory is explained to a model; the runtime composes none of it. */
|
|
19
|
+
const MEMORY_RULES = 'agents/memory-instructions.md';
|
|
20
|
+
/** The only place forking is explained to a model; the same holds. */
|
|
21
|
+
const FORK_RULES = 'agents/fork-instructions.md';
|
|
22
|
+
/** The conditioned documents `zen` ships, which land whether or not they apply. */
|
|
23
|
+
const OURS = new Set([MEMORY_RULES, FORK_RULES]);
|
|
17
24
|
const PROMPTS_DIR = 'agents/prompts';
|
|
18
25
|
const SKILLS_DIR = 'agents/skills';
|
|
19
26
|
const SKILL_FILE = 'SKILL.md';
|
|
20
27
|
const ASSETS_DIR = 'assets';
|
|
21
28
|
/** What `allow:` and `preload:` accept, so a skill outside it cannot be named. */
|
|
22
29
|
const REFERABLE = /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/;
|
|
30
|
+
/** What `requires:` may name. Mirrors `CAPABILITIES` in the loader. */
|
|
31
|
+
const CAPABILITIES = {
|
|
32
|
+
fork: (spec) => Boolean(spec.fork),
|
|
33
|
+
memory: (spec) => Boolean(spec.memory),
|
|
34
|
+
'memory-write': (spec) => spec.memory?.access === 'read-write' || spec.memory?.access === 'full',
|
|
35
|
+
'memory-forget': (spec) => spec.memory?.access === 'full',
|
|
36
|
+
};
|
|
23
37
|
/**
|
|
24
38
|
* Mirrors `readHouseRules` in the loader, in the same order:
|
|
25
39
|
* `agents/instructions.md`, then every `agents/<topic>-instructions.md` by
|
|
26
|
-
* filename.
|
|
40
|
+
* filename — each with the `requires:` its frontmatter declared.
|
|
27
41
|
*/
|
|
28
42
|
function houseRules(root) {
|
|
29
43
|
const found = [];
|
|
30
44
|
const take = (rel) => {
|
|
31
|
-
|
|
32
|
-
|
|
45
|
+
const path = join(root, rel);
|
|
46
|
+
if (!existsSync(path)) {
|
|
47
|
+
return;
|
|
33
48
|
}
|
|
49
|
+
const { data } = frontmatter(readFileSync(path, 'utf8'));
|
|
50
|
+
found.push({ rel, requires: toList(data.requires) });
|
|
34
51
|
};
|
|
35
52
|
take(HOUSE_RULES);
|
|
36
53
|
const dir = join(root, AGENTS_DIR);
|
|
@@ -46,6 +63,15 @@ function houseRules(root) {
|
|
|
46
63
|
}
|
|
47
64
|
return found;
|
|
48
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* The documents this agent's prompt actually gets. Unknown capabilities are
|
|
68
|
+
* reported once, against the file, rather than silently excluding every agent.
|
|
69
|
+
*/
|
|
70
|
+
function rulesFor(rules, spec) {
|
|
71
|
+
return rules
|
|
72
|
+
.filter((r) => r.requires.every((cap) => CAPABILITIES[cap]?.(spec) ?? true))
|
|
73
|
+
.map((r) => r.rel);
|
|
74
|
+
}
|
|
49
75
|
export async function validateProject(opts) {
|
|
50
76
|
const root = resolve(opts.dir);
|
|
51
77
|
const findings = [];
|
|
@@ -169,7 +195,7 @@ export async function validateProject(opts) {
|
|
|
169
195
|
// Files the config and the conventions name
|
|
170
196
|
// -----------------------------------------------------------------------
|
|
171
197
|
const rules = houseRules(root);
|
|
172
|
-
for (const rel of rules) {
|
|
198
|
+
for (const { rel, requires } of rules) {
|
|
173
199
|
record(rel, 'house rules — prepended to every agent prompt', 'file', false);
|
|
174
200
|
if (empty(join(root, rel))) {
|
|
175
201
|
add({
|
|
@@ -180,6 +206,31 @@ export async function validateProject(opts) {
|
|
|
180
206
|
fix: 'write the rules that hold whichever agent is answering, or delete it',
|
|
181
207
|
});
|
|
182
208
|
}
|
|
209
|
+
for (const cap of requires) {
|
|
210
|
+
if (!(cap in CAPABILITIES)) {
|
|
211
|
+
add({
|
|
212
|
+
severity: 'error',
|
|
213
|
+
code: 'rules.requires.unknown',
|
|
214
|
+
where: rel,
|
|
215
|
+
message: `\`requires: ${cap}\` names no capability, so the loader refuses the ` +
|
|
216
|
+
'project',
|
|
217
|
+
fix: `use one of: ${Object.keys(CAPABILITIES).join(', ')}`,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Ours are copied into every project whether or not it uses the
|
|
222
|
+
// capability, so an inert one says nothing about the project. One the
|
|
223
|
+
// project wrote is different: it was written to be read.
|
|
224
|
+
const reached = config.agents.some((a) => rulesFor([{ rel, requires }], a).length > 0);
|
|
225
|
+
if (requires.length && !reached && !OURS.has(rel)) {
|
|
226
|
+
add({
|
|
227
|
+
severity: 'note',
|
|
228
|
+
code: 'rules.unreached',
|
|
229
|
+
where: rel,
|
|
230
|
+
message: `no agent has ${requires.join(' and ')}, so this document reaches no prompt`,
|
|
231
|
+
fix: 'relax `requires:`, or give an agent the capability it is written for',
|
|
232
|
+
});
|
|
233
|
+
}
|
|
183
234
|
}
|
|
184
235
|
if (rules.length === 0) {
|
|
185
236
|
record(HOUSE_RULES, 'house rules — prepended to every agent prompt', 'file', false);
|
|
@@ -191,6 +242,61 @@ export async function validateProject(opts) {
|
|
|
191
242
|
'prompt alone, with nothing shared between them',
|
|
192
243
|
});
|
|
193
244
|
}
|
|
245
|
+
// The runtime composes the preference block and nothing else, so this file
|
|
246
|
+
// is the whole of what an agent is told about memory. Without it the tools
|
|
247
|
+
// are still granted and still callable, with only their schemas to go on -
|
|
248
|
+
// which is not a project that works less well, it is one that will write
|
|
249
|
+
// the graph wrong and cannot be told otherwise.
|
|
250
|
+
if (config.agents.some((a) => a.memory) || config.memory) {
|
|
251
|
+
const path = join(root, MEMORY_RULES);
|
|
252
|
+
if (!existsSync(path) || empty(path)) {
|
|
253
|
+
add({
|
|
254
|
+
severity: 'error',
|
|
255
|
+
code: 'memory.uninstructed',
|
|
256
|
+
where: MEMORY_RULES,
|
|
257
|
+
message: 'memory is on, but nothing tells an agent how to use it — the memory_* ' +
|
|
258
|
+
'tools are granted with only their schemas to explain them',
|
|
259
|
+
fix: 'zen check --fix, which writes the current rules back',
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// The same again for forking, and for the same reason: the runtime used to
|
|
264
|
+
// compose this block and no longer does, so the file is the whole of what
|
|
265
|
+
// an agent is told about what survives a join.
|
|
266
|
+
if (config.agents.some((a) => a.fork)) {
|
|
267
|
+
const path = join(root, FORK_RULES);
|
|
268
|
+
if (!existsSync(path) || empty(path)) {
|
|
269
|
+
add({
|
|
270
|
+
severity: 'error',
|
|
271
|
+
code: 'fork.uninstructed',
|
|
272
|
+
where: FORK_RULES,
|
|
273
|
+
message: 'an agent can fork, but nothing tells it what a branch can and cannot do — ' +
|
|
274
|
+
'the fork tool is granted with only its schema to explain it',
|
|
275
|
+
fix: 'zen check --fix, which writes the current rules back',
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Present is not the same as current. These three files are copies of ours
|
|
280
|
+
// and nobody is meant to be maintaining them, so bytes that differ are an
|
|
281
|
+
// older `zen`'s — prose about a runtime that has since moved, which reads
|
|
282
|
+
// exactly as authoritative as the version that is true. The frontmatter
|
|
283
|
+
// that conditions them arrived this way: every project scaffolded before it
|
|
284
|
+
// carries a copy that reaches every agent, memory or no memory.
|
|
285
|
+
for (const rel of staleShared(root)) {
|
|
286
|
+
// An empty one is already reported, as missing or as empty.
|
|
287
|
+
if (empty(join(root, rel))) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
add({
|
|
291
|
+
severity: 'warning',
|
|
292
|
+
code: 'rules.stale',
|
|
293
|
+
where: rel,
|
|
294
|
+
message: `${rel} is not the copy this \`zen\` ships — it came from an older version, ` +
|
|
295
|
+
'or was edited in place, and either way it describes behaviour that may no ' +
|
|
296
|
+
'longer be the behaviour',
|
|
297
|
+
fix: 'zen check --fix, which replaces it; rules of your own belong in a topic file beside it',
|
|
298
|
+
});
|
|
299
|
+
}
|
|
194
300
|
// -----------------------------------------------------------------------
|
|
195
301
|
// Agents
|
|
196
302
|
// -----------------------------------------------------------------------
|
|
@@ -211,7 +317,7 @@ export async function validateProject(opts) {
|
|
|
211
317
|
// against the real set rather than a list written down twice.
|
|
212
318
|
const available = availableTools(root, config);
|
|
213
319
|
for (const spec of config.agents) {
|
|
214
|
-
agents.push(checkAgent(root, config, spec, entry, available, record, add));
|
|
320
|
+
agents.push(checkAgent(root, config, spec, entry, available, rules, record, add));
|
|
215
321
|
}
|
|
216
322
|
checkReturnPaths(config, add);
|
|
217
323
|
// -----------------------------------------------------------------------
|
|
@@ -430,9 +536,9 @@ export function availableTools(root, config) {
|
|
|
430
536
|
...exaTools(),
|
|
431
537
|
];
|
|
432
538
|
}
|
|
433
|
-
function checkAgent(root, config, spec, entry, available, record, add) {
|
|
539
|
+
function checkAgent(root, config, spec, entry, available, rules, record, add) {
|
|
434
540
|
const where = `agents.${spec.name}`;
|
|
435
|
-
const instructions =
|
|
541
|
+
const instructions = rulesFor(rules, spec);
|
|
436
542
|
// The role prompt: named by `system:`, or found by convention. A named one
|
|
437
543
|
// that is not there is an error — the loader says so too. An absent
|
|
438
544
|
// conventional one is not, because a project may keep everything it has to
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenera/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.21",
|
|
4
4
|
"description": "Command-line front end for @zenera/neo: agentic projects you can run, share and commit.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"@inkjs/ui": "^2.0.0",
|
|
52
52
|
"ink": "^7.1.1",
|
|
53
53
|
"react": "^19.2.8",
|
|
54
|
-
"@zenera/neo": "^1.1.
|
|
54
|
+
"@zenera/neo": "^1.1.21",
|
|
55
55
|
"@anthropic-ai/sdk": "^0.120.0",
|
|
56
56
|
"@google/genai": "^2.18.0",
|
|
57
57
|
"@openrouter/sdk": "^1.2.80",
|
|
@@ -222,7 +222,8 @@ my-project/
|
|
|
222
222
|
├── agents.yaml who exists, what they may reach for
|
|
223
223
|
├── agents/
|
|
224
224
|
│ ├── instructions.md house rules, prepended to every agent
|
|
225
|
-
│ ├──
|
|
225
|
+
│ ├── fork-instructions.md more of them - any `<topic>-instructions.md`
|
|
226
|
+
│ ├── memory-instructions.md and more - how the graph is used
|
|
226
227
|
│ ├── tools-instructions.md and more - how tools are called
|
|
227
228
|
│ ├── prompts/
|
|
228
229
|
│ │ ├── intake.md one agent's own brief
|
|
@@ -588,6 +589,21 @@ when `memory:` is turned on and are deleted when it is turned off, which is a
|
|
|
588
589
|
whole file rather than a section somebody has to find. Do not split by author,
|
|
589
590
|
by date, or to keep a file short; that is what a skill is for.
|
|
590
591
|
|
|
592
|
+
A document about one capability can say so in frontmatter, and then reaches only
|
|
593
|
+
the agents that have it:
|
|
594
|
+
|
|
595
|
+
```markdown
|
|
596
|
+
---
|
|
597
|
+
requires: [fork]
|
|
598
|
+
---
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
All of the list has to hold. The vocabulary is closed - `fork`, `memory`,
|
|
602
|
+
`memory-write`, `memory-forget` - and anything else is a load error. There is no
|
|
603
|
+
`not`: a rule for agents _without_ a capability is a rule about the rest of the
|
|
604
|
+
project, which is what `agents/instructions.md` is for. No frontmatter means
|
|
605
|
+
unconditional, which is the usual case.
|
|
606
|
+
|
|
591
607
|
Put in them only what is true for **every** agent:
|
|
592
608
|
|
|
593
609
|
- identity and domain ("You work the property claims desk")
|
|
@@ -1299,7 +1315,8 @@ remembered file's path comes from `memory_load` and not from listing `/memory`,
|
|
|
1299
1315
|
what a node must carry to be worth having, and that a correction is a
|
|
1300
1316
|
`SUPERSEDES` edge rather than an edit. It is its own file rather than a section of
|
|
1301
1317
|
`agents/instructions.md` because it arrives with the `memory:` block and leaves
|
|
1302
|
-
with it
|
|
1318
|
+
with it - and it carries `requires: [memory]`, so an agent without the store
|
|
1319
|
+
never reads a word of it.
|
|
1303
1320
|
|
|
1304
1321
|
**Leave that file as a pure copy.** Anything this project decides for itself -
|
|
1305
1322
|
which audience holds what, what must never be written down, what an agent is
|
|
@@ -1483,11 +1500,15 @@ agent's prompt which one this work wants:
|
|
|
1483
1500
|
lookups.
|
|
1484
1501
|
|
|
1485
1502
|
Declaring `fork:` only makes the tool available. What a fork costs and what
|
|
1486
|
-
survives the join is
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1503
|
+
survives the join is in `agents/fork-instructions.md`, which carries
|
|
1504
|
+
`requires: [fork]` and so reaches exactly the agents holding the key. That file
|
|
1505
|
+
is `zen`'s, not yours - `zen check --fix` replaces it, editing it or carrying an
|
|
1506
|
+
older copy is reported as `rules.stale`, and deleting it leaves your forking
|
|
1507
|
+
agents with the tool schema alone (`fork.uninstructed`). What is
|
|
1508
|
+
left to you is _when_, in the terms of this domain - _"When the request covers
|
|
1509
|
+
more than one region, fork one branch per region and merge their findings"_ - or
|
|
1510
|
+
a weaker model will work through the list serially and never call it. Project
|
|
1511
|
+
policy on forking goes in a topic file of your own beside it.
|
|
1491
1512
|
|
|
1492
1513
|
### 6.5 Termination
|
|
1493
1514
|
|
|
@@ -1918,7 +1939,10 @@ Before finishing any change here:
|
|
|
1918
1939
|
agent-specific
|
|
1919
1940
|
- [ ] Nothing duplicated between a house-rules file and an agent prompt
|
|
1920
1941
|
- [ ] Each `agents/<topic>-instructions.md` is about one capability, and is true
|
|
1921
|
-
of every agent
|
|
1942
|
+
of every agent it reaches - `requires:` if it is about one of them
|
|
1943
|
+
- [ ] If any agent has `fork:`, `agents/fork-instructions.md` is present - §6.4
|
|
1944
|
+
- [ ] No `rules.stale`: the three files that are `zen`'s rather than yours say
|
|
1945
|
+
what this version of the runtime does, and `zen check --fix` replaces them
|
|
1922
1946
|
- [ ] Every tool and agent referenced by its exact name
|
|
1923
1947
|
- [ ] Failure paths stated for every instruction that can fail
|
|
1924
1948
|
- [ ] No facts, rates or figures embedded in a prompt
|
|
@@ -2031,6 +2055,7 @@ candidates; these are the judgements to make about each one)**
|
|
|
2031
2055
|
| Loses a detail after a handoff | Say it in the handoff; check the collapse policy |
|
|
2032
2056
|
| Works through N independent items serially | `fork:` on that agent, and a prompt line - §6.4 |
|
|
2033
2057
|
| Forks when the steps actually depend | Prompt line: branches cannot see each other |
|
|
2058
|
+
| Forks a sequence, or loses a branch's working | `agents/fork-instructions.md` is missing or empty - §6.4 |
|
|
2034
2059
|
| Slow and expensive on trivial cases | Demote that agent's model tier / reasoning effort |
|
|
2035
2060
|
| Fails only on genuinely hard cases | Promote that agent's tier, or split the hard path out |
|
|
2036
2061
|
| Shows no reasoning while it works | Turn summaries on for that model - §7.6 |
|
|
@@ -21,7 +21,8 @@ What it writes:
|
|
|
21
21
|
zenera.json { version, name }
|
|
22
22
|
agents.yaml the configuration
|
|
23
23
|
agents/instructions.md house rules, prepended to every agent
|
|
24
|
-
agents/
|
|
24
|
+
agents/fork-instructions.md more of them, for agents that can fork
|
|
25
|
+
agents/memory-instructions.md more of them, for agents with the memory store
|
|
25
26
|
agents/tools-instructions.md more of them, about how tools are called
|
|
26
27
|
agents/prompts/default.md the default agent's prompt
|
|
27
28
|
agents/skills/ empty, for skills
|
|
@@ -31,13 +32,16 @@ sessions/ empty
|
|
|
31
32
|
.env this project's environment, git-ignored
|
|
32
33
|
.gitignore
|
|
33
34
|
.vscode/settings.json editor files
|
|
35
|
+
.vscode/agents.schema.json what validates agents.yaml as you type it
|
|
34
36
|
.github/ copilot instructions, prompts, this skill
|
|
35
37
|
```
|
|
36
38
|
|
|
37
39
|
More house rules can be added by hand: any `agents/<topic>-instructions.md` is
|
|
38
|
-
read too, in filename order, and prepended to every agent
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
read too, in filename order, and prepended to every agent - or, with
|
|
41
|
+
`requires: [<capability>]` in its frontmatter, to every agent that has the
|
|
42
|
+
capability it is about. A capability that needs standing rules - memory, say -
|
|
43
|
+
gets its own file rather than another section of `agents/instructions.md`.
|
|
44
|
+
Memory's are not written from scratch:
|
|
41
45
|
`agents/memory-instructions.md` is the same file as
|
|
42
46
|
`.github/skills/zen-memory/references/memory-instructions.md`, which init puts
|
|
43
47
|
in the project as well, so a copy that was lost or went stale is restored rather
|
|
@@ -46,9 +50,16 @@ which of the two it is, and `… fix` does the copy.
|
|
|
46
50
|
|
|
47
51
|
The project's own files are never overwritten - `--force` is what allows
|
|
48
52
|
writing into an occupied directory, and the files already there stay. The
|
|
49
|
-
editor files are the exception: `.vscode
|
|
50
|
-
are
|
|
51
|
-
|
|
53
|
+
editor files are the exception: `.vscode/` and the `.github/` tree are ours and
|
|
54
|
+
are replaced on every `init` and every `zen open`, so edits to them do not
|
|
55
|
+
survive.
|
|
56
|
+
|
|
57
|
+
`.vscode/agents.schema.json` describes `agents.yaml` to the editor, so an
|
|
58
|
+
unknown key is underlined where it is typed and every key carries its
|
|
59
|
+
documentation on hover. It needs the `redhat.vscode-yaml` extension, which
|
|
60
|
+
`.vscode/extensions.json` recommends. It is an editor aid and nothing else: it
|
|
61
|
+
is rewritten from the version of `zen` in hand, and `zen check` is still what
|
|
62
|
+
says whether a project loads.
|
|
52
63
|
|
|
53
64
|
The default agent gets the file tools and a sandboxed shell, plus `exa:*` when
|
|
54
65
|
the keyring holds an Exa key. It also gets memory: the store is scaffolded in
|
|
@@ -102,7 +113,7 @@ The editor is chosen in this order: `--editor`, `$ZENERA_EDITOR`, the editor
|
|
|
102
113
|
this terminal belongs to, `$VISUAL` or `$EDITOR`, a known editor on `PATH` or
|
|
103
114
|
installed, then the platform opener.
|
|
104
115
|
|
|
105
|
-
Opening refreshes the editor files (`.vscode
|
|
116
|
+
Opening refreshes the editor files (`.vscode/` and the `.github/`
|
|
106
117
|
tree) in the directory being opened. VS Code and its forks are launched with
|
|
107
118
|
`--disable-workspace-trust`, so the settings written there apply to the new
|
|
108
119
|
window immediately rather than after a prompt.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: zen-memory
|
|
3
|
-
description: How agent memory is organised and how to configure it in `agents.yaml` - the `memory:` block, per-agent `access`/`sees`/`writes`/`autoRecall`, the four `memory_*` tools, kinds and relations, how recall ranks and stitches a subgraph, and how to design a memory strategy for a project. Includes the usage rules every memory-enabled project must carry in `agents/memory-instructions.md` (references/memory-instructions.md)
|
|
3
|
+
description: How agent memory is organised and how to configure it in `agents.yaml` - the `memory:` block, per-agent `access`/`sees`/`writes`/`autoRecall`, the four `memory_*` tools, kinds and relations, how recall ranks and stitches a subgraph, and how to design a memory strategy for a project. Includes the default usage rules every memory-enabled project must carry in `agents/memory-instructions.md` (references/memory-instructions.md), the script that says whether that copy is still current (scripts/check-instructions.sh), where a project's own customisations go instead (`agents/memory-policy-instructions.md`, under `requires: [memory]`), what to commit and what never to, how to keep a working file under `/memory` and re-run it later, and how to put memory in front of a `zen rag schema` or `zen rag docs` index so a search that already succeeded once is not paid for again.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Memory
|
|
@@ -112,10 +112,14 @@ asked for; counting that as use would poison recency ranking. Commit is one
|
|
|
112
112
|
call because a remembered thing is a subgraph, and building it with three calls
|
|
113
113
|
leaves the graph half-formed when the model stops early.
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
recollection, when committing is worthwhile, why correction is a new
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
`agents/memory-instructions.md` already explains all of this to the agent - how
|
|
116
|
+
to read a recollection, when committing is worthwhile, why correction is a new
|
|
117
|
+
node. It is house rules under `requires: [memory]`, so it reaches exactly the
|
|
118
|
+
agents that have the store and no others, and it gates its own advice on the
|
|
119
|
+
tools the reader actually holds. The runtime adds the preference block and
|
|
120
|
+
nothing else. Do not restate any of it in a prompt; state project-specific
|
|
121
|
+
policy instead, and keep the copy byte-identical to the reference so
|
|
122
|
+
`check-instructions.sh` stays meaningful.
|
|
119
123
|
|
|
120
124
|
## How it is organised
|
|
121
125
|
|
|
@@ -328,6 +332,13 @@ Commit one only when the user's own words generalise: "always", "from now on",
|
|
|
328
332
|
instruction, and a graph full of false preferences is a system prompt nobody
|
|
329
333
|
wrote.
|
|
330
334
|
|
|
335
|
+
What the user states about themselves is the same kind of node, for the same
|
|
336
|
+
reason - the name to address them by, their role, their timezone, which of
|
|
337
|
+
several environments is theirs. Asking again next run is exactly what the block
|
|
338
|
+
exists to prevent. Credentials and secrets are not preferences, however they
|
|
339
|
+
were offered; `agents/memory-policy-instructions.md` is where a project says so
|
|
340
|
+
in its own terms.
|
|
341
|
+
|
|
331
342
|
## Remembering files, and running them again
|
|
332
343
|
|
|
333
344
|
The workspace is disposable - it may be a container about to be discarded - so a
|
|
@@ -501,8 +512,8 @@ An index used by a project needs a skill in that project describing it - see
|
|
|
501
512
|
index** belongs in that skill, because it is the same subject and it is read at
|
|
502
513
|
the moment the index is. Policy that holds whatever the agent is doing -
|
|
503
514
|
audiences, what must never be written down - goes in
|
|
504
|
-
`agents/memory-policy-instructions.md` instead, which every agent
|
|
505
|
-
it loads the skill or not.
|
|
515
|
+
`agents/memory-policy-instructions.md` instead, which every agent with the store
|
|
516
|
+
gets whether it loads the skill or not.
|
|
506
517
|
|
|
507
518
|
```md
|
|
508
519
|
- Recalled routes are a shortcut, not authority. Confirm with `list_api --name`
|
|
@@ -559,11 +570,42 @@ prepended to every agent, in filename order, so this belongs in its own document
|
|
|
559
570
|
rather than as another section of `agents/instructions.md` - it arrives with
|
|
560
571
|
memory and it leaves with it.
|
|
561
572
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
573
|
+
### Two files, and which is which
|
|
574
|
+
|
|
575
|
+
| File | Is | Owned by |
|
|
576
|
+
| -------------------------------------- | ------------------------------------------------------------------ | ----------- |
|
|
577
|
+
| `agents/memory-instructions.md` | the DEFAULT rules - how the store works, and how any agent uses it | this skill |
|
|
578
|
+
| `agents/memory-policy-instructions.md` | this PROJECT's customisations on top of them | the project |
|
|
579
|
+
|
|
580
|
+
**Keep the first a pure copy.** It is predefined: it describes this version of
|
|
581
|
+
the runtime, not this project, and `check-instructions.sh` compares it byte for
|
|
582
|
+
byte. Anything edited into it is lost the next time the reference changes.
|
|
583
|
+
|
|
584
|
+
Everything a project decides for itself goes in the second - audiences, what
|
|
585
|
+
must never be written down, what an agent commits at the end of a job, which
|
|
586
|
+
index a recalled route is checked against. Filename order puts it directly
|
|
587
|
+
after the defaults, so it reads as the exceptions to them. Then keeping up with
|
|
588
|
+
a changed reference is the same one command again, rather than a hand-patch
|
|
589
|
+
around prose that has to be preserved.
|
|
590
|
+
|
|
591
|
+
**It must open with the same frontmatter**, or it is prepended to every agent in
|
|
592
|
+
the project, including the ones with no store to apply it to:
|
|
593
|
+
|
|
594
|
+
```md
|
|
595
|
+
---
|
|
596
|
+
requires: [memory]
|
|
597
|
+
---
|
|
598
|
+
|
|
599
|
+
# Memory policy
|
|
600
|
+
|
|
601
|
+
- Never commit a customer name or an account id; a `fact` says which tenant
|
|
602
|
+
shape it was, never whose.
|
|
603
|
+
- ...
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
`requires: [memory]` is what delivers a topic file on the capability it is
|
|
607
|
+
about. Use `requires: [memory, memory-write]` for a rule only a committing agent
|
|
608
|
+
can act on.
|
|
567
609
|
|
|
568
610
|
## Inspecting and repairing it
|
|
569
611
|
|
|
@@ -635,7 +677,8 @@ agents:
|
|
|
635
677
|
|
|
636
678
|
- [ ] `.github/skills/zen-memory/scripts/check-instructions.sh` exits zero, so
|
|
637
679
|
`agents/memory-instructions.md` is the reference byte for byte - and
|
|
638
|
-
project policy lives in `agents/memory-policy-instructions.md
|
|
680
|
+
project policy lives in `agents/memory-policy-instructions.md`, opening
|
|
681
|
+
with `requires: [memory]`.
|
|
639
682
|
- [ ] `memory.embedding` is set, and was set before the graph had content.
|
|
640
683
|
- [ ] Every agent that should learn has a `memory:` binding - the top-level
|
|
641
684
|
block alone enables nothing.
|
|
@@ -653,23 +696,24 @@ agents:
|
|
|
653
696
|
|
|
654
697
|
## When it goes wrong
|
|
655
698
|
|
|
656
|
-
| Symptom | Cause
|
|
657
|
-
| --------------------------------------------------- |
|
|
658
|
-
| The `memory_*` tools are missing | No `memory:` on the agent. They come from the binding, never from `tools:`
|
|
659
|
-
| `zen memory` says the project has none | Neither a `memory:` block nor an agent binding - nothing is opened and no directory is made
|
|
660
|
-
| Recall finds a memory only when reworded | No embedder. `zen memory stats` says `embedding none`
|
|
661
|
-
| Recall finds nothing after changing the embedder | Refused rather than mixed - a manifest records the model. Change it back, or start a new memory
|
|
662
|
-
| Vectors fewer than nodes | Some nodes were committed with no embedder; they are only reachable by term overlap
|
|
663
|
-
| The graph fills with restated requests | The commit rule is not in a prompt or skill. State the "would a later run redo this" test
|
|
664
|
-
|
|
|
665
|
-
|
|
|
666
|
-
|
|
|
667
|
-
|
|
|
668
|
-
|
|
|
669
|
-
|
|
|
670
|
-
|
|
|
671
|
-
|
|
|
672
|
-
|
|
|
673
|
-
|
|
|
674
|
-
|
|
|
675
|
-
|
|
|
699
|
+
| Symptom | Cause |
|
|
700
|
+
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
701
|
+
| The `memory_*` tools are missing | No `memory:` on the agent. They come from the binding, never from `tools:` |
|
|
702
|
+
| `zen memory` says the project has none | Neither a `memory:` block nor an agent binding - nothing is opened and no directory is made |
|
|
703
|
+
| Recall finds a memory only when reworded | No embedder. `zen memory stats` says `embedding none` |
|
|
704
|
+
| Recall finds nothing after changing the embedder | Refused rather than mixed - a manifest records the model. Change it back, or start a new memory |
|
|
705
|
+
| Vectors fewer than nodes | Some nodes were committed with no embedder; they are only reachable by term overlap |
|
|
706
|
+
| The graph fills with restated requests | The commit rule is not in a prompt or skill. State the "would a later run redo this" test |
|
|
707
|
+
| Runs do real work and the graph stays empty | The agent is bound `read`, or nothing names the moments: empty recall then work, before an answer, a handoff or a fork's return |
|
|
708
|
+
| An agent greps `/memory` or reads `graph.json` | The house-rules block is missing from `agents/memory-instructions.md` - the system prompt never forbids it |
|
|
709
|
+
| A recalled fact cannot be checked or continued | Nodes were committed with no provenance. The block's "say where it came from" rule is what prevents it |
|
|
710
|
+
| Recall returns pointers and the run re-reads it all | Content was committed as `text` alone. A node's text describes and locates; the content belongs in a file |
|
|
711
|
+
| A wrong memory keeps coming back | It was edited instead of superseded, or superseded in the wrong direction - the **new** node is the source |
|
|
712
|
+
| An agent cannot see a node you can | Its `audience` is not in that agent's `sees`. Invisible and missing are the same thing, on purpose |
|
|
713
|
+
| "this agent cannot remember files" | The agent has no workspace, so a path cannot be resolved. Commit without `file` |
|
|
714
|
+
| `error: outside the workspace: /tmp/…` | The file is real to the shell and outside the only tree memory reads. Write it under `/workspace` |
|
|
715
|
+
| Long content arrives as `text`, never as a `file` | The agent has no tool that writes to the workspace, so it has no path to name |
|
|
716
|
+
| A remembered file will not run | It is not self-contained - it referenced the workspace it was written in |
|
|
717
|
+
| Recollections are large and unhelpful | `autoRecall.limit` is too high, or node texts describe answers rather than questions |
|
|
718
|
+
| The model recalls a route the API no longer has | Nothing re-checks memory against a rebuilt index. Supersede, and require verification |
|
|
719
|
+
| A second run of the same project refuses to start | The directory lock. A lock whose process is gone is stale and is taken over |
|
|
@@ -101,7 +101,8 @@ show_diff() {
|
|
|
101
101
|
fix() {
|
|
102
102
|
cp "$REFERENCE" "$COPY"
|
|
103
103
|
echo "wrote: $COPY"
|
|
104
|
-
echo "note: project policy belongs in agents/memory-policy-instructions.md"
|
|
104
|
+
echo "note: project policy belongs in agents/memory-policy-instructions.md,"
|
|
105
|
+
echo " which must open with '---' / 'requires: [memory]' / '---'"
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
case "${1:-status}" in
|