@zenera/cli 1.1.24 → 1.1.25
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 +53 -0
- package/dist/ask.js +28 -5
- package/dist/banner.js +2 -2
- package/dist/batch.d.ts +6 -0
- package/dist/batch.js +76 -1
- package/dist/catalog.d.ts +11 -1
- package/dist/catalog.js +80 -7
- package/dist/commands/inspect.d.ts +47 -1
- package/dist/commands/inspect.js +167 -18
- package/dist/commands/memory.js +128 -29
- package/dist/commands/meta.js +311 -21
- package/dist/commands/models.js +96 -12
- package/dist/commands/open.js +4 -4
- package/dist/commands/run.js +38 -4
- package/dist/commands/sandbox.js +76 -14
- package/dist/engine.d.ts +5 -0
- package/dist/engine.js +1 -0
- package/dist/eventlog.d.ts +59 -0
- package/dist/eventlog.js +146 -0
- package/dist/liveness.d.ts +7 -0
- package/dist/liveness.js +15 -1
- package/dist/meta/editor.d.ts +9 -0
- package/dist/meta/editor.js +60 -0
- package/dist/meta/finetune/children.d.ts +8 -0
- package/dist/meta/finetune/children.js +76 -0
- package/dist/meta/finetune/command.d.ts +30 -0
- package/dist/meta/finetune/command.js +338 -0
- package/dist/meta/finetune/dataset/anchors.d.ts +41 -0
- package/dist/meta/finetune/dataset/anchors.js +239 -0
- package/dist/meta/finetune/dataset/changes.d.ts +36 -0
- package/dist/meta/finetune/dataset/changes.js +319 -0
- package/dist/meta/finetune/dataset/command.d.ts +17 -0
- package/dist/meta/finetune/dataset/command.js +1011 -0
- package/dist/meta/finetune/dataset/drift.d.ts +33 -0
- package/dist/meta/finetune/dataset/drift.js +71 -0
- package/dist/meta/finetune/dataset/export.d.ts +26 -0
- package/dist/meta/finetune/dataset/export.js +51 -0
- package/dist/meta/finetune/dataset/index.d.ts +2 -0
- package/dist/meta/finetune/dataset/index.js +2 -0
- package/dist/meta/finetune/dataset/sample.d.ts +50 -0
- package/dist/meta/finetune/dataset/sample.js +197 -0
- package/dist/meta/finetune/dataset/schema.d.ts +33 -0
- package/dist/meta/finetune/dataset/schema.js +216 -0
- package/dist/meta/finetune/dataset/store.d.ts +35 -0
- package/dist/meta/finetune/dataset/store.js +177 -0
- package/dist/meta/finetune/dataset/types.d.ts +125 -0
- package/dist/meta/finetune/dataset/types.js +39 -0
- package/dist/meta/finetune/failure.d.ts +15 -0
- package/dist/meta/finetune/failure.js +26 -0
- package/dist/meta/finetune/feedback.d.ts +45 -0
- package/dist/meta/finetune/feedback.js +85 -0
- package/dist/meta/finetune/improvements.d.ts +51 -0
- package/dist/meta/finetune/improvements.js +244 -0
- package/dist/meta/finetune/index.d.ts +2 -0
- package/dist/meta/finetune/index.js +2 -0
- package/dist/meta/finetune/journal.d.ts +35 -0
- package/dist/meta/finetune/journal.js +135 -0
- package/dist/meta/finetune/live.d.ts +74 -0
- package/dist/meta/finetune/live.js +226 -0
- package/dist/meta/finetune/loop.d.ts +3 -0
- package/dist/meta/finetune/loop.js +114 -0
- package/dist/meta/finetune/memories.d.ts +47 -0
- package/dist/meta/finetune/memories.js +154 -0
- package/dist/meta/finetune/report.d.ts +33 -0
- package/dist/meta/finetune/report.js +1284 -0
- package/dist/meta/finetune/retry.d.ts +24 -0
- package/dist/meta/finetune/retry.js +96 -0
- package/dist/meta/finetune/sampler.d.ts +18 -0
- package/dist/meta/finetune/sampler.js +41 -0
- package/dist/meta/finetune/system.d.ts +27 -0
- package/dist/meta/finetune/system.js +132 -0
- package/dist/meta/finetune/train.d.ts +39 -0
- package/dist/meta/finetune/train.js +391 -0
- package/dist/meta/finetune/tui.d.ts +16 -0
- package/dist/meta/finetune/tui.js +291 -0
- package/dist/meta/finetune/tuning.d.ts +167 -0
- package/dist/meta/finetune/tuning.js +289 -0
- package/dist/meta/finetune/usage.d.ts +62 -0
- package/dist/meta/finetune/usage.js +225 -0
- package/dist/meta/host.d.ts +14 -0
- package/dist/meta/host.js +20 -0
- package/dist/meta/index.d.ts +11 -0
- package/dist/meta/index.js +11 -0
- package/dist/meta/launch.d.ts +81 -0
- package/dist/meta/launch.js +286 -0
- package/dist/meta/model.d.ts +44 -0
- package/dist/meta/model.js +119 -0
- package/dist/meta/prompts.d.ts +18 -0
- package/dist/meta/prompts.js +61 -0
- package/dist/meta/relay.d.ts +20 -0
- package/dist/meta/relay.js +123 -0
- package/dist/meta/tokens.d.ts +52 -0
- package/dist/meta/tokens.js +226 -0
- package/dist/meta/window.d.ts +61 -0
- package/dist/meta/window.js +198 -0
- package/dist/meta/wire.d.ts +42 -0
- package/dist/meta/wire.js +129 -0
- package/dist/modelref.d.ts +16 -0
- package/dist/modelref.js +41 -0
- package/dist/narrate.js +5 -2
- package/dist/podman.d.ts +26 -0
- package/dist/podman.js +83 -3
- package/dist/projects.d.ts +0 -5
- package/dist/projects.js +2 -15
- package/dist/sandbox.d.ts +2 -0
- package/dist/sandbox.js +10 -3
- package/dist/scaffold.d.ts +15 -6
- package/dist/scaffold.js +5 -60
- package/dist/session.js +4 -29
- package/dist/term.d.ts +2 -0
- package/dist/term.js +11 -2
- package/dist/trace.js +53 -2
- package/dist/tui/models.d.ts +33 -0
- package/dist/tui/models.js +389 -0
- package/dist/tui/wrap.d.ts +5 -0
- package/dist/tui/wrap.js +12 -0
- package/dist/usage.d.ts +78 -0
- package/dist/usage.js +51 -0
- package/package.json +3 -2
- package/templates/editor/.github/copilot-instructions.md +31 -102
- package/templates/editor/.github/prompts/analyze.prompt.md +31 -0
- package/templates/editor/.github/prompts/dataset.prompt.md +108 -0
- package/templates/editor/.github/prompts/finetune-apply.prompt.md +91 -0
- package/templates/editor/.github/prompts/finetune.prompt.md +691 -198
- package/templates/editor/.github/prompts/new-agent.prompt.md +3 -1
- package/templates/editor/.github/prompts/new-skill.prompt.md +2 -0
- package/templates/editor/.github/prompts/project-review.prompt.md +4 -3
- package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +483 -0
- package/templates/editor/.github/skills/zen-cli/SKILL.md +9 -1
- package/templates/editor/.github/skills/zen-cli/references/dataset.md +34 -0
- package/templates/editor/.github/skills/zen-cli/references/finetune.md +77 -0
- package/templates/editor/.github/skills/zen-cli/references/inspect.md +47 -9
- package/templates/editor/.github/skills/zen-cli/references/memory.md +33 -10
- package/templates/editor/.github/skills/zen-cli/references/meta.md +61 -0
- package/templates/editor/.github/skills/zen-cli/references/models.md +5 -1
- package/templates/editor/.github/skills/zen-cli/references/rag.md +24 -6
- package/templates/editor/.github/skills/zen-cli/references/sandbox.md +10 -1
- package/templates/editor/.github/skills/zen-code-python/SKILL.md +123 -0
- package/templates/editor/.github/skills/zen-code-python/references/code_python.md +48 -0
- package/templates/editor/.github/skills/zen-dataset/SKILL.md +253 -0
- package/templates/editor/.github/skills/zen-finetune/SKILL.md +1449 -658
- package/templates/editor/.github/skills/zen-finetune/references/readme-template.md +268 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/batch.mjs +390 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/difficult.mjs +243 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/memory.mjs +280 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/next.mjs +398 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/report.mjs +797 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/select.mjs +354 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/usage.mjs +722 -0
- package/templates/editor/.github/skills/zen-inspect/SKILL.md +60 -25
- package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +270 -0
- package/templates/editor/.github/skills/zen-memory/SKILL.md +18 -14
- package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +20 -8
- package/templates/editor/.github/skills/zen-review/SKILL.md +5 -3
- package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +5 -4
- package/templates/editor/.github/skills/zen-sandbox/SKILL.md +7 -3
- package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +2 -2
- package/templates/editor/.github/skills/zen-topology/SKILL.md +283 -0
- package/templates/editor/.vscode/settings.json +1 -0
- package/templates/project/agents/files-instructions.md +29 -20
- package/templates/project/agents/memory-instructions.md +31 -32
- package/templates/project/agents/tools-instructions.md +7 -4
- package/templates/project/gitignore +2 -0
- package/dist/meta.d.ts +0 -221
- package/dist/meta.js +0 -684
- package/templates/editor/.github/skills/zen-finetune/scripts/collect.sh +0 -432
- package/templates/editor/.github/skills/zen-finetune/scripts/sample.sh +0 -199
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { closeSync, fstatSync, mkdirSync, openSync, readSync, renameSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
import { modelName, STAGES } from "./usage.js";
|
|
4
|
+
const KEEP_LINES = 200;
|
|
5
|
+
const KEEP_LOG = 50;
|
|
6
|
+
const KEEP_ERRORS = 5;
|
|
7
|
+
const PHASE_WORD = { nomem: 'no memory', mem: 'with memory' };
|
|
8
|
+
/** Not log rows: a start shows in `now` and ends in an event of its own, an error has its own section. */
|
|
9
|
+
const UNLOGGED = new Set(['run', 'analyze', 'apply', 'memory merge', 'idle', 'error']);
|
|
10
|
+
const blank = () => ({ calls: 0, input: 0, output: 0, byModel: {}, lines: [] });
|
|
11
|
+
function fold(a, text) {
|
|
12
|
+
let e;
|
|
13
|
+
try {
|
|
14
|
+
e = JSON.parse(text);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
a.lines.push(e);
|
|
20
|
+
if (a.lines.length > KEEP_LINES) {
|
|
21
|
+
a.lines.shift();
|
|
22
|
+
}
|
|
23
|
+
if (e.type === 'llm') {
|
|
24
|
+
const model = modelName(e.model);
|
|
25
|
+
a.model = model;
|
|
26
|
+
a.calls++;
|
|
27
|
+
a.input += e.in;
|
|
28
|
+
a.output += e.out;
|
|
29
|
+
const m = (a.byModel[model] ??= { calls: 0, input: 0, output: 0 });
|
|
30
|
+
m.calls++;
|
|
31
|
+
m.input += e.in;
|
|
32
|
+
m.output += e.out;
|
|
33
|
+
}
|
|
34
|
+
else if (e.type === 'tool' && e.phase === 'start') {
|
|
35
|
+
a.last = `${e.name} ${e.subject}`.trim();
|
|
36
|
+
}
|
|
37
|
+
else if (e.type === 'say') {
|
|
38
|
+
a.last = e.text;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Reads only what each events file gained since the last look; a file cleared for a new step starts over. */
|
|
42
|
+
export class Tails {
|
|
43
|
+
#files = new Map();
|
|
44
|
+
read(file) {
|
|
45
|
+
let fd;
|
|
46
|
+
try {
|
|
47
|
+
fd = openSync(file, 'r');
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
this.#files.delete(file);
|
|
51
|
+
return blank();
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const st = fstatSync(fd);
|
|
55
|
+
let f = this.#files.get(file);
|
|
56
|
+
if (!f || f.ino !== st.ino || st.size < f.offset) {
|
|
57
|
+
f = { ino: st.ino, offset: 0, rest: '', act: blank() };
|
|
58
|
+
this.#files.set(file, f);
|
|
59
|
+
}
|
|
60
|
+
if (st.size > f.offset) {
|
|
61
|
+
const buf = Buffer.alloc(st.size - f.offset);
|
|
62
|
+
readSync(fd, buf, 0, buf.length, f.offset);
|
|
63
|
+
f.offset = st.size;
|
|
64
|
+
const parts = (f.rest + buf.toString('utf8')).split('\n');
|
|
65
|
+
f.rest = parts.pop() ?? '';
|
|
66
|
+
for (const line of parts) {
|
|
67
|
+
fold(f.act, line);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return f.act;
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
closeSync(fd);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Forgets the files no step writes any more. */
|
|
77
|
+
keep(files) {
|
|
78
|
+
for (const file of this.#files.keys()) {
|
|
79
|
+
if (!files.has(file)) {
|
|
80
|
+
this.#files.delete(file);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const tails = new WeakMap();
|
|
86
|
+
function stateOf(t) {
|
|
87
|
+
if (t.finished) {
|
|
88
|
+
return t.stopping ? 'stopped' : 'finished';
|
|
89
|
+
}
|
|
90
|
+
return t.stopping ? 'stopping' : 'running';
|
|
91
|
+
}
|
|
92
|
+
function logRow(e) {
|
|
93
|
+
const where = e.phase ? `${PHASE_WORD[e.phase]}${e.attempt ? ` try ${e.attempt}` : ''}` : '';
|
|
94
|
+
return {
|
|
95
|
+
at: e.at,
|
|
96
|
+
action: e.what,
|
|
97
|
+
case: [e.case, where].filter(Boolean).join(' · '),
|
|
98
|
+
result: e.result ?? e.detail ?? '',
|
|
99
|
+
...(e.took !== undefined ? { took: e.took } : {}),
|
|
100
|
+
...(e.tokens ? { tokens: e.tokens } : {}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export function snapshot(t) {
|
|
104
|
+
let tail = tails.get(t);
|
|
105
|
+
if (!tail) {
|
|
106
|
+
tail = new Tails();
|
|
107
|
+
tails.set(t, tail);
|
|
108
|
+
}
|
|
109
|
+
const now = [];
|
|
110
|
+
const files = new Set();
|
|
111
|
+
const watch = (file) => {
|
|
112
|
+
files.add(file);
|
|
113
|
+
return tail.read(file);
|
|
114
|
+
};
|
|
115
|
+
const clock = Date.now();
|
|
116
|
+
for (const w of t.workers) {
|
|
117
|
+
if (!w.case || (w.step !== 'run' && w.step !== 'analyze')) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const dir = t.attemptDir(w.case, w.phase ?? 'nomem', w.attempt ?? 1);
|
|
121
|
+
now.push({
|
|
122
|
+
key: `w${w.slot}`,
|
|
123
|
+
who: `worker ${w.slot}`,
|
|
124
|
+
step: w.step,
|
|
125
|
+
what: `${w.case.id} · ${PHASE_WORD[w.phase ?? 'nomem']} try ${w.attempt ?? 1}`,
|
|
126
|
+
sinceMs: clock - w.since,
|
|
127
|
+
stage: w.step,
|
|
128
|
+
act: watch(join(dir, w.step === 'run' ? 'events.jsonl' : 'analyze.events.jsonl')),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const q = t.improvements;
|
|
132
|
+
if (q.current?.dir) {
|
|
133
|
+
now.push({
|
|
134
|
+
key: 'apply',
|
|
135
|
+
who: 'applier',
|
|
136
|
+
step: 'apply',
|
|
137
|
+
what: `apply ${basename(q.current.dir)} · ${q.current.requests} request(s)`,
|
|
138
|
+
sinceMs: clock - q.current.since,
|
|
139
|
+
stage: 'apply',
|
|
140
|
+
act: watch(join(q.current.dir, 'apply.events.jsonl')),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
else if (q.current) {
|
|
144
|
+
now.push({
|
|
145
|
+
key: 'apply',
|
|
146
|
+
who: 'applier',
|
|
147
|
+
step: 'waiting',
|
|
148
|
+
what: `${q.pending.length} request(s), ${q.runs} run(s) to finish first`,
|
|
149
|
+
sinceMs: clock - q.current.since,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const merge = t.memories.merging;
|
|
153
|
+
if (merge) {
|
|
154
|
+
now.push({
|
|
155
|
+
key: 'merge',
|
|
156
|
+
who: 'memory',
|
|
157
|
+
step: 'merge',
|
|
158
|
+
what: merge.name,
|
|
159
|
+
sinceMs: clock - merge.since,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
tail.keep(files);
|
|
163
|
+
const sums = new Map();
|
|
164
|
+
const add = (stage, model, s) => {
|
|
165
|
+
const key = `${stage}\u0000${model}`;
|
|
166
|
+
const row = sums.get(key) ?? { stage, model, calls: 0, input: 0, output: 0 };
|
|
167
|
+
row.calls += s.calls;
|
|
168
|
+
row.input += s.input;
|
|
169
|
+
row.output += s.output;
|
|
170
|
+
sums.set(key, row);
|
|
171
|
+
};
|
|
172
|
+
for (const stage of STAGES) {
|
|
173
|
+
for (const [model, s] of Object.entries(t.progress?.spent[stage] ?? {})) {
|
|
174
|
+
add(stage, model, s);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
for (const row of now) {
|
|
178
|
+
for (const [model, s] of Object.entries(row.act?.byModel ?? {})) {
|
|
179
|
+
add(row.stage, model, s);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const tokens = [...sums.values()]
|
|
183
|
+
.filter((r) => r.calls > 0)
|
|
184
|
+
.sort((a, b) => STAGES.indexOf(a.stage) - STAGES.indexOf(b.stage) ||
|
|
185
|
+
b.input + b.output - (a.input + a.output));
|
|
186
|
+
return {
|
|
187
|
+
pid: process.pid,
|
|
188
|
+
project: basename(t.root),
|
|
189
|
+
state: stateOf(t),
|
|
190
|
+
at: new Date(clock).toISOString(),
|
|
191
|
+
elapsedMs: clock - t.startedAt,
|
|
192
|
+
system: t.system.version(),
|
|
193
|
+
workers: { busy: t.busy(), all: t.workers.length },
|
|
194
|
+
total: t.progress?.total ?? 0,
|
|
195
|
+
counts: t.progress?.counts ?? {},
|
|
196
|
+
parked: q.pending.length,
|
|
197
|
+
applyAt: t.config.applyAt,
|
|
198
|
+
now,
|
|
199
|
+
tokens,
|
|
200
|
+
log: t.recent
|
|
201
|
+
.filter((e) => !UNLOGGED.has(e.what))
|
|
202
|
+
.slice(0, KEEP_LOG)
|
|
203
|
+
.map(logRow),
|
|
204
|
+
errors: t.errors.slice(0, KEEP_ERRORS),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/** Takes a snapshot, keeps it in live.json without the raw lines, and hands it to every watcher. */
|
|
208
|
+
export function publish(t) {
|
|
209
|
+
let snap;
|
|
210
|
+
try {
|
|
211
|
+
snap = snapshot(t);
|
|
212
|
+
const file = join(t.dir, 'live.json');
|
|
213
|
+
mkdirSync(t.dir, { recursive: true });
|
|
214
|
+
writeFileSync(`${file}.tmp`, `${JSON.stringify(snap, (k, v) => (k === 'lines' ? undefined : v), 2)}\n`);
|
|
215
|
+
renameSync(`${file}.tmp`, file);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// A live view that failed to draw must not stop the tuning.
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
for (const watcher of t.watchers) {
|
|
222
|
+
watcher(snap);
|
|
223
|
+
}
|
|
224
|
+
return snap;
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=live.js.map
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { claimLock, ownLock } from '@zenera/neo';
|
|
2
|
+
import { mkdirSync, rmSync, unlinkSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { CliError, EXIT } from "../host.js";
|
|
5
|
+
import { StepError } from "./failure.js";
|
|
6
|
+
import { recover } from "./journal.js";
|
|
7
|
+
import { publish } from "./live.js";
|
|
8
|
+
import { nextCase, resultOf } from "./sampler.js";
|
|
9
|
+
import { markFailed, SandboxDown, train, VoidRun } from "./train.js";
|
|
10
|
+
import { Stopped } from "./tuning.js";
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// The loop: N workers, cases started as they come free, one applier
|
|
13
|
+
//
|
|
14
|
+
// Whenever a worker is free - and no woken case is waiting for one - the next
|
|
15
|
+
// case starts on it and is trained to the end. A parked case gives its worker
|
|
16
|
+
// back, so a lane never sits idle waiting for an apply. A stop never interrupts
|
|
17
|
+
// a step: it is noticed between steps, and the case resumes next time.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
const REPORT_EVERY_MS = 15_000;
|
|
20
|
+
const LIVE_EVERY_MS = 1000;
|
|
21
|
+
export async function runLoop(t) {
|
|
22
|
+
mkdirSync(t.dir, { recursive: true });
|
|
23
|
+
const lock = join(t.dir, 'loop.lock');
|
|
24
|
+
claimLock(lock, ownLock(), (held) => new CliError(`a tuning is already running here (pid ${held.pid}, since ${held.startedAt})`, EXIT.failed, `stop it with: zen meta finetune stop - or remove ${lock} if it crashed`));
|
|
25
|
+
rmSync(t.stopFile, { force: true });
|
|
26
|
+
const timer = setInterval(() => t.report(), REPORT_EVERY_MS);
|
|
27
|
+
const live = setInterval(() => publish(t), LIVE_EVERY_MS);
|
|
28
|
+
try {
|
|
29
|
+
await recover(t);
|
|
30
|
+
const start = t.system.record('start');
|
|
31
|
+
t.event({ what: 'started', detail: `system v${start.v}, ${t.workers.length} workers` });
|
|
32
|
+
publish(t);
|
|
33
|
+
const applier = t.improvements.loop();
|
|
34
|
+
await dispatch(t);
|
|
35
|
+
t.finished = true;
|
|
36
|
+
await applier;
|
|
37
|
+
await t.memories.finish();
|
|
38
|
+
t.event({ what: t.stopping ? 'stopped' : 'finished' });
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
clearInterval(timer);
|
|
42
|
+
clearInterval(live);
|
|
43
|
+
t.finished = true;
|
|
44
|
+
publish(t);
|
|
45
|
+
try {
|
|
46
|
+
unlinkSync(lock);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Taken over as stale; nothing to give back.
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (t.fatal) {
|
|
53
|
+
throw t.fatal;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function dispatch(t) {
|
|
57
|
+
const running = new Set();
|
|
58
|
+
while (!t.stopping) {
|
|
59
|
+
if (t.improvements.applying || t.resuming > 0 || !t.freeWorker()) {
|
|
60
|
+
await t.tick();
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const c = nextCase(t);
|
|
64
|
+
if (!c) {
|
|
65
|
+
t.drained = true;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
const seat = { case: c };
|
|
69
|
+
t.claimed.add(c.id);
|
|
70
|
+
t.tried.add(c.id);
|
|
71
|
+
const before = resultOf(t, c);
|
|
72
|
+
if (before?.state === 'failed') {
|
|
73
|
+
// Taken again: until it ends anew it is in progress, not failed. Its analyze session
|
|
74
|
+
// may be what broke (another model, a dead provider), so the next analysis starts fresh.
|
|
75
|
+
rmSync(join(t.caseDir(c), 'result.json'), { force: true });
|
|
76
|
+
rmSync(join(t.caseDir(c), 'session'), { force: true });
|
|
77
|
+
t.event({ what: 'resumed', case: c.id, rev: c.rev, detail: before.reason });
|
|
78
|
+
}
|
|
79
|
+
await t.seat(seat, false);
|
|
80
|
+
const task = trainCase(t, seat).finally(() => running.delete(task));
|
|
81
|
+
running.add(task);
|
|
82
|
+
}
|
|
83
|
+
await Promise.all(running);
|
|
84
|
+
}
|
|
85
|
+
async function trainCase(t, s) {
|
|
86
|
+
const c = s.case;
|
|
87
|
+
try {
|
|
88
|
+
await train(t, s, c);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
if (!(err instanceof Stopped)) {
|
|
92
|
+
const why = err instanceof SandboxDown
|
|
93
|
+
? `sandbox: ${err.message}`
|
|
94
|
+
: err instanceof VoidRun
|
|
95
|
+
? `void: ${err.message}`
|
|
96
|
+
: `error: ${err.message}`;
|
|
97
|
+
markFailed(t, c, s.phase ?? 'nomem', why);
|
|
98
|
+
const attempt = s.worker?.attempt;
|
|
99
|
+
t.error(`${c.id} · ${s.phase === 'mem' ? 'with memory' : 'no memory'}${attempt ? ` try ${attempt}` : ''}`, err.message, err instanceof StepError ? err.log : undefined);
|
|
100
|
+
if (err instanceof SandboxDown) {
|
|
101
|
+
t.sandboxDown(err.message);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
t.failed(why);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
t.unseat(s);
|
|
110
|
+
t.claimed.delete(c.id);
|
|
111
|
+
t.report();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=loop.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Case } from './dataset/types.ts';
|
|
2
|
+
import type { Run } from './train.ts';
|
|
3
|
+
import { type Tuning } from './tuning.ts';
|
|
4
|
+
export interface Submission {
|
|
5
|
+
/** `<case>@r<rev>` */
|
|
6
|
+
key: string;
|
|
7
|
+
case: string;
|
|
8
|
+
rev: number;
|
|
9
|
+
attempt: number;
|
|
10
|
+
run: string;
|
|
11
|
+
dir: string;
|
|
12
|
+
at: string;
|
|
13
|
+
/** the run committed nothing: there is no graph to merge */
|
|
14
|
+
empty: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface MergeRow {
|
|
17
|
+
n: number;
|
|
18
|
+
name: string;
|
|
19
|
+
at: string;
|
|
20
|
+
dir: string;
|
|
21
|
+
log: string;
|
|
22
|
+
/** every submission in this graph */
|
|
23
|
+
includes: string[];
|
|
24
|
+
/** the ones this merge brought in */
|
|
25
|
+
added: string[];
|
|
26
|
+
ok: boolean;
|
|
27
|
+
nodes?: number;
|
|
28
|
+
edges?: number;
|
|
29
|
+
}
|
|
30
|
+
export declare class Memories {
|
|
31
|
+
#private;
|
|
32
|
+
merging: {
|
|
33
|
+
name: string;
|
|
34
|
+
since: number;
|
|
35
|
+
} | undefined;
|
|
36
|
+
constructor(t: Tuning);
|
|
37
|
+
get dir(): string;
|
|
38
|
+
submissions(): Submission[];
|
|
39
|
+
merges(): MergeRow[];
|
|
40
|
+
lastMerge(): MergeRow | undefined;
|
|
41
|
+
unmerged(): Submission[];
|
|
42
|
+
/** Keeps a copy of a passing with-memory run's memory. Once per case revision. */
|
|
43
|
+
submit(c: Case, attempt: number, run: Run): void;
|
|
44
|
+
/** Merges whatever is left, and waits for every merge queued. */
|
|
45
|
+
finish(): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=memories.d.ts.map
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { appendFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
import { DatasetStore } from "./dataset/store.js";
|
|
4
|
+
import { errorIn, sizeOf } from "./failure.js";
|
|
5
|
+
import { writeFeedback } from "./report.js";
|
|
6
|
+
import { readJson } from "./tuning.js";
|
|
7
|
+
function rows(file) {
|
|
8
|
+
try {
|
|
9
|
+
return readFileSync(file, 'utf8')
|
|
10
|
+
.split('\n')
|
|
11
|
+
.filter(Boolean)
|
|
12
|
+
.map((line) => JSON.parse(line));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export class Memories {
|
|
19
|
+
#t;
|
|
20
|
+
#chain = Promise.resolve();
|
|
21
|
+
merging;
|
|
22
|
+
constructor(t) {
|
|
23
|
+
this.#t = t;
|
|
24
|
+
}
|
|
25
|
+
get dir() {
|
|
26
|
+
return join(this.#t.dir, 'memories');
|
|
27
|
+
}
|
|
28
|
+
submissions() {
|
|
29
|
+
return rows(join(this.dir, 'submissions.jsonl'));
|
|
30
|
+
}
|
|
31
|
+
merges() {
|
|
32
|
+
return rows(join(this.dir, 'merges.jsonl'));
|
|
33
|
+
}
|
|
34
|
+
lastMerge() {
|
|
35
|
+
return this.merges()
|
|
36
|
+
.filter((m) => m.ok)
|
|
37
|
+
.at(-1);
|
|
38
|
+
}
|
|
39
|
+
unmerged() {
|
|
40
|
+
const merged = new Set(this.lastMerge()?.includes ?? []);
|
|
41
|
+
return this.submissions().filter((s) => !s.empty && !merged.has(s.key));
|
|
42
|
+
}
|
|
43
|
+
/** Keeps a copy of a passing with-memory run's memory. Once per case revision. */
|
|
44
|
+
submit(c, attempt, run) {
|
|
45
|
+
const key = `${c.id}@r${c.rev}`;
|
|
46
|
+
if (this.submissions().some((s) => s.key === key)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const dir = join(this.dir, 'submitted', key);
|
|
50
|
+
const empty = !existsSync(join(run.memory, 'manifest.json'));
|
|
51
|
+
rmSync(dir, { recursive: true, force: true });
|
|
52
|
+
if (!empty) {
|
|
53
|
+
cpSync(run.memory, dir, {
|
|
54
|
+
recursive: true,
|
|
55
|
+
filter: (src) => basename(src) !== '.lock',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const row = {
|
|
59
|
+
key,
|
|
60
|
+
case: c.id,
|
|
61
|
+
rev: c.rev,
|
|
62
|
+
attempt,
|
|
63
|
+
run: run.dir,
|
|
64
|
+
dir,
|
|
65
|
+
at: new Date().toISOString(),
|
|
66
|
+
empty,
|
|
67
|
+
};
|
|
68
|
+
mkdirSync(this.dir, { recursive: true });
|
|
69
|
+
appendFileSync(join(this.dir, 'submissions.jsonl'), `${JSON.stringify(row)}\n`);
|
|
70
|
+
this.#t.event({
|
|
71
|
+
what: 'memory submitted',
|
|
72
|
+
case: c.id,
|
|
73
|
+
rev: c.rev,
|
|
74
|
+
detail: empty ? 'nothing committed' : `${this.unmerged().length} waiting to merge`,
|
|
75
|
+
});
|
|
76
|
+
if (this.unmerged().length >= this.#t.config.mergeEvery) {
|
|
77
|
+
this.#queue();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Merges whatever is left, and waits for every merge queued. */
|
|
81
|
+
async finish() {
|
|
82
|
+
if (this.unmerged().length > 0) {
|
|
83
|
+
this.#queue();
|
|
84
|
+
}
|
|
85
|
+
await this.#chain;
|
|
86
|
+
}
|
|
87
|
+
#queue() {
|
|
88
|
+
this.#chain = this.#chain
|
|
89
|
+
.then(() => this.#merge())
|
|
90
|
+
.catch((err) => {
|
|
91
|
+
this.#t.error('memory merge', err.message);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async #merge() {
|
|
95
|
+
const fresh = this.unmerged();
|
|
96
|
+
if (fresh.length === 0) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const n = this.merges().length + 1;
|
|
100
|
+
const name = `m${String(n).padStart(2, '0')}`;
|
|
101
|
+
const target = join(this.dir, 'merged', name);
|
|
102
|
+
const log = join(this.dir, 'merged', `${name}.log`);
|
|
103
|
+
await this.#t.journal.step({ kind: 'merge', dir: target, log }, () => this.#mergeInto(fresh, n, name, target, log));
|
|
104
|
+
}
|
|
105
|
+
async #mergeInto(fresh, n, name, target, log) {
|
|
106
|
+
const last = this.lastMerge();
|
|
107
|
+
rmSync(target, { recursive: true, force: true });
|
|
108
|
+
mkdirSync(join(this.dir, 'merged'), { recursive: true });
|
|
109
|
+
const began = Date.now();
|
|
110
|
+
const from = sizeOf(log);
|
|
111
|
+
this.merging = { name, since: began };
|
|
112
|
+
this.#t.event({ what: 'memory merge', detail: `${name}: ${fresh.length} new` });
|
|
113
|
+
const sources = [...(last ? [last.dir] : []), ...fresh.map((s) => s.dir)];
|
|
114
|
+
const res = await this.#t
|
|
115
|
+
.zen(['memory', 'merge', ...sources, '--dir', target, '--yes'], log)
|
|
116
|
+
.finally(() => {
|
|
117
|
+
this.merging = undefined;
|
|
118
|
+
});
|
|
119
|
+
const manifest = readJson(join(target, 'manifest.json'));
|
|
120
|
+
const row = {
|
|
121
|
+
n,
|
|
122
|
+
name,
|
|
123
|
+
at: new Date().toISOString(),
|
|
124
|
+
dir: target,
|
|
125
|
+
log,
|
|
126
|
+
includes: [...(last?.includes ?? []), ...fresh.map((s) => s.key)],
|
|
127
|
+
added: fresh.map((s) => s.key),
|
|
128
|
+
ok: res.code === 0 && manifest !== undefined,
|
|
129
|
+
...(manifest?.nodes !== undefined ? { nodes: manifest.nodes } : {}),
|
|
130
|
+
...(manifest?.edges !== undefined ? { edges: manifest.edges } : {}),
|
|
131
|
+
};
|
|
132
|
+
appendFileSync(join(this.dir, 'merges.jsonl'), `${JSON.stringify(row)}\n`);
|
|
133
|
+
if (!row.ok) {
|
|
134
|
+
const e = errorIn(log, from);
|
|
135
|
+
this.#t.error(`memory ${name}`, e.message ?? 'the merge wrote no manifest', e.log);
|
|
136
|
+
}
|
|
137
|
+
const store = DatasetStore.open(this.#t.root);
|
|
138
|
+
for (const s of fresh) {
|
|
139
|
+
const c = store.get(s.case);
|
|
140
|
+
if (c?.rev === s.rev) {
|
|
141
|
+
writeFeedback(this.#t, c);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
this.#t.event({
|
|
145
|
+
what: row.ok ? 'memory merged' : 'memory merge failed',
|
|
146
|
+
detail: `${name}: ${row.includes.length} case(s)${row.ok ? '' : ' - see the log'}`,
|
|
147
|
+
result: row.ok
|
|
148
|
+
? `${row.includes.length} case(s), ${row.nodes ?? '?'} nodes`
|
|
149
|
+
: 'see the log',
|
|
150
|
+
took: Date.now() - began,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=memories.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type Case } from './dataset/types.ts';
|
|
2
|
+
import { type Feedback, type Phase } from './feedback.ts';
|
|
3
|
+
import type { Run } from './train.ts';
|
|
4
|
+
import { type Tuning } from './tuning.ts';
|
|
5
|
+
import { type ByStage } from './usage.ts';
|
|
6
|
+
export interface Attempt {
|
|
7
|
+
phase: Phase;
|
|
8
|
+
attempt: number;
|
|
9
|
+
dir: string;
|
|
10
|
+
run?: Run;
|
|
11
|
+
feedback?: Feedback;
|
|
12
|
+
}
|
|
13
|
+
/** A table cell: one line, no column breaks. */
|
|
14
|
+
export declare const cell: (text: string | number | undefined) => string;
|
|
15
|
+
export declare function since(ms: number): string;
|
|
16
|
+
export declare function attemptsOf(t: Tuning, c: Case): Attempt[];
|
|
17
|
+
/** Every try of a case, per stage and model. Applies serve many cases at once, so they are not split. */
|
|
18
|
+
export declare function caseTokens(t: Tuning, c: Case): ByStage;
|
|
19
|
+
export declare function feedbackPath(t: Tuning, c: {
|
|
20
|
+
id: string;
|
|
21
|
+
}): string;
|
|
22
|
+
export declare function writeFeedback(t: Tuning, c: Case): void;
|
|
23
|
+
export declare function patchFiles(patch: string): {
|
|
24
|
+
file: string;
|
|
25
|
+
added: number;
|
|
26
|
+
removed: number;
|
|
27
|
+
text: string;
|
|
28
|
+
}[];
|
|
29
|
+
export declare function applyPath(dir: string): string;
|
|
30
|
+
export declare function writeApply(t: Tuning, dir: string): void;
|
|
31
|
+
export declare function writeStatus(t: Tuning): void;
|
|
32
|
+
export declare function markdownPage(title: string, md: string, live: boolean): string;
|
|
33
|
+
//# sourceMappingURL=report.d.ts.map
|