@manyoars/oars 0.5.4 → 0.5.5
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 +4 -4
- package/dist/cli.js +18 -7
- package/dist/club.d.ts +2 -0
- package/dist/club.js +10 -4
- package/dist/sessions/archive.js +1 -1
- package/dist/sessions/card-command.d.ts +2 -2
- package/dist/sessions/card-command.js +7 -5
- package/dist/sessions/card-line.d.ts +6 -2
- package/dist/sessions/card-line.js +6 -3
- package/dist/sessions/cell-increments.d.ts +7 -0
- package/dist/sessions/cell-increments.js +25 -10
- package/dist/sessions/cells.d.ts +7 -0
- package/dist/sessions/cells.js +118 -107
- package/dist/sessions/crew-mark.d.ts +6 -0
- package/dist/sessions/crew-mark.js +22 -0
- package/dist/sessions/hook-index.d.ts +2 -1
- package/dist/sessions/hook-index.js +2 -2
- package/dist/sessions/pool-command.d.ts +22 -4
- package/dist/sessions/pool-command.js +516 -82
- package/dist/sessions/pool-delta.d.ts +5 -0
- package/dist/sessions/pool-delta.js +32 -12
- package/dist/sessions/pool-fold.d.ts +22 -0
- package/dist/sessions/pool-fold.js +65 -0
- package/dist/sessions/pool-outbox.d.ts +9 -0
- package/dist/sessions/pool-outbox.js +33 -12
- package/dist/sessions/pool.d.ts +10 -5
- package/dist/sessions/pool.js +16 -15
- package/dist/sessions/read-call-shape.js +3 -2
- package/dist/sessions/read-folds.d.ts +21 -0
- package/dist/sessions/read-folds.js +37 -0
- package/dist/sessions/read-recovery.d.ts +1 -1
- package/dist/sessions/read-recovery.js +6 -5
- package/dist/sessions/read-support.d.ts +4 -1
- package/dist/sessions/recall-consequence.d.ts +2 -1
- package/dist/sessions/recall-consequence.js +1 -2
- package/dist/sessions/recall-reads.d.ts +23 -1
- package/dist/sessions/recall-reads.js +91 -63
- package/dist/sessions/reversion.d.ts +41 -2
- package/dist/sessions/reversion.js +17 -10
- package/dist/sessions/store.d.ts +55 -2
- package/dist/sessions/store.js +312 -61
- package/dist/telemetry/measures.d.ts +5 -0
- package/dist/telemetry/measures.js +16 -2
- package/dist/telemetry/schema.js +3 -1
- package/dist/telemetry/words.d.ts +6 -0
- package/dist/telemetry/words.js +4 -0
- package/package.json +1 -1
|
@@ -52,6 +52,11 @@ export interface Carried {
|
|
|
52
52
|
* again under the same key, builds none it already carried. Nothing is marked here: the caller marks them once the batch is written.
|
|
53
53
|
*/
|
|
54
54
|
export declare const carriedDeltas: (account: string, sessions: readonly Gated[], sent: ReadonlySet<string>) => Map<string, Carried>;
|
|
55
|
+
/**
|
|
56
|
+
* The same deltas as `carriedDeltas`, one window at a time: `sessions` are placed from their session records alone, and `load` reads the full records of the ids one window needs (all of the window before, the first `DELTA_MIN_SESSIONS` after).
|
|
57
|
+
* What is held at once is one window's records, never the history.
|
|
58
|
+
*/
|
|
59
|
+
export declare const carriedDeltasByWindow: (account: string, sessions: readonly Gated[], sent: ReadonlySet<string>, load: (ids: ReadonlySet<string>) => Promise<ReadonlyMap<string, SessionRecords>>, fillers?: ReadonlySet<string>) => Promise<Map<string, Carried>>;
|
|
55
60
|
/** The delta entries the batch under `key` in this outbox already carries; none when it has no batch, or one this reader does not take. */
|
|
56
61
|
export declare const heldDeltas: (outbox: string, key: string) => Promise<DeltaEntry[]>;
|
|
57
62
|
/**
|
|
@@ -173,19 +173,39 @@ export const placedOf = (records) => {
|
|
|
173
173
|
export const carriedDeltas = (account, sessions, sent) => {
|
|
174
174
|
const carried = new Map();
|
|
175
175
|
for (const { index, before, after, filler } of windowsOf(sessions)) {
|
|
176
|
-
const entries = [];
|
|
177
|
-
const marks = [];
|
|
178
|
-
for (const entry of deltasOf(before.map(({ records }) => records),
|
|
179
176
|
// The outcome is fixed at the fill: sessions after the filler change nothing, so a delta is the same whenever it is built.
|
|
180
|
-
after.slice(0, DELTA_MIN_SESSIONS).map(({ records }) => records))
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
177
|
+
const found = carriedIn(account, index, before.map(({ records }) => records), after.slice(0, DELTA_MIN_SESSIONS).map(({ records }) => records), sent);
|
|
178
|
+
if (found.entries.length > 0)
|
|
179
|
+
carried.set(filler.id, found);
|
|
180
|
+
}
|
|
181
|
+
return carried;
|
|
182
|
+
};
|
|
183
|
+
const carriedIn = (account, index, before, after, sent) => {
|
|
184
|
+
const found = { entries: [], marks: [] };
|
|
185
|
+
for (const entry of deltasOf(before, after)) {
|
|
186
|
+
const mark = deltaMark(account, entry, index);
|
|
187
|
+
if (sent.has(mark))
|
|
188
|
+
continue;
|
|
189
|
+
found.entries.push(entry);
|
|
190
|
+
found.marks.push(mark);
|
|
191
|
+
}
|
|
192
|
+
return found;
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* The same deltas as `carriedDeltas`, one window at a time: `sessions` are placed from their session records alone, and `load` reads the full records of the ids one window needs (all of the window before, the first `DELTA_MIN_SESSIONS` after).
|
|
196
|
+
* What is held at once is one window's records, never the history.
|
|
197
|
+
*/
|
|
198
|
+
export const carriedDeltasByWindow = async (account, sessions, sent, load, fillers) => {
|
|
199
|
+
const carried = new Map();
|
|
200
|
+
for (const { index, before, after, filler } of windowsOf(sessions)) {
|
|
201
|
+
// Only the batch of the session that fills a window carries its delta, so a window whose filler is not being built is not read.
|
|
202
|
+
if (fillers !== undefined && !fillers.has(filler.id))
|
|
203
|
+
continue;
|
|
204
|
+
const needed = [...before, ...after.slice(0, DELTA_MIN_SESSIONS)];
|
|
205
|
+
const records = await load(new Set(needed.map(({ id }) => id)));
|
|
206
|
+
const found = carriedIn(account, index, before.flatMap(({ id }) => records.get(id) ?? []), after.slice(0, DELTA_MIN_SESSIONS).flatMap(({ id }) => records.get(id) ?? []), sent);
|
|
207
|
+
if (found.entries.length > 0)
|
|
208
|
+
carried.set(filler.id, found);
|
|
189
209
|
}
|
|
190
210
|
return carried;
|
|
191
211
|
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CellSet } from './cells.js';
|
|
2
|
+
import { type ReadFolds } from './read-folds.js';
|
|
3
|
+
import type { Recall } from './recall-object.js';
|
|
4
|
+
import { type ReversionIndex } from './reversion.js';
|
|
5
|
+
import type { Store } from './store.js';
|
|
6
|
+
/** What the pool step reads in place of the whole store's tools and prompts. */
|
|
7
|
+
export interface PoolFolds {
|
|
8
|
+
/** The recalls in `recalls.jsonl` order, each of a held session with its consequence. */
|
|
9
|
+
recalls: Recall[];
|
|
10
|
+
cells: CellSet;
|
|
11
|
+
reads: ReadFolds;
|
|
12
|
+
}
|
|
13
|
+
export interface PoolFold {
|
|
14
|
+
/** One run of sessions: their main and subagent records, prompts, tools, and own errors. A session of another run is not in it. */
|
|
15
|
+
add: (run: Store) => void;
|
|
16
|
+
finish: () => PoolFolds;
|
|
17
|
+
/** The matching's input as it stands, for a report of its size. */
|
|
18
|
+
index: ReversionIndex;
|
|
19
|
+
}
|
|
20
|
+
export declare const poolFold: (recalls: readonly Recall[]) => PoolFold;
|
|
21
|
+
/** The folds of a store held whole: one run. */
|
|
22
|
+
export declare const poolFoldsOf: (recalls: readonly Recall[], store: Store) => PoolFolds;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// What `refreshPool` needs of the rows of every turn, folded one run of sessions at a time (t1243, spec 4 Build 2): each recall's consequence, the cells, and the two read families' inputs.
|
|
2
|
+
// A run is a set of sessions with their prompts, tools, and own errors; the reversion matching waits for the last run, so `reverted` is filled in `finish`.
|
|
3
|
+
import { cellsFold, projectedStore } from './cell-increments.js';
|
|
4
|
+
import { shellReader } from './projection.js';
|
|
5
|
+
import { readFold } from './read-folds.js';
|
|
6
|
+
import { deriveConsequences, NO_RECALLS, recallGraphOf } from './recall-consequence.js';
|
|
7
|
+
import { finishReversions, foldReversions, newReversionIndex } from './reversion.js';
|
|
8
|
+
import { vocabulary } from './vocab-load.js';
|
|
9
|
+
export const poolFold = (recalls) => {
|
|
10
|
+
const bySid = new Map();
|
|
11
|
+
for (const [at, recall] of recalls.entries()) {
|
|
12
|
+
if (recall.sid === null)
|
|
13
|
+
continue;
|
|
14
|
+
bySid.set(recall.sid, [...(bySid.get(recall.sid) ?? []), { at, recall }]);
|
|
15
|
+
}
|
|
16
|
+
const derived = [...recalls];
|
|
17
|
+
const index = newReversionIndex();
|
|
18
|
+
// The cells are tallied with the graph of the run being added.
|
|
19
|
+
let graph = NO_RECALLS;
|
|
20
|
+
const cells = cellsFold({ place: (tool, steps) => graph.place(tool, steps), exposedPrompt: (session, at) => graph.exposedPrompt(session, at) });
|
|
21
|
+
const reads = readFold();
|
|
22
|
+
const read = shellReader(vocabulary());
|
|
23
|
+
// The recalls whose outcome holds a reversion lookup, which the last run settles.
|
|
24
|
+
const pending = [];
|
|
25
|
+
const add = (run) => {
|
|
26
|
+
foldReversions(index, run);
|
|
27
|
+
// In `recalls.jsonl` order, as the whole store's call keeps them.
|
|
28
|
+
const mine = run.sessions.flatMap((session) => bySid.get(session.session) ?? []).sort((a, b) => a.at - b.at);
|
|
29
|
+
const asked = [];
|
|
30
|
+
const placeholder = (session, prompt) => {
|
|
31
|
+
asked.push({ session, prompt });
|
|
32
|
+
return null;
|
|
33
|
+
};
|
|
34
|
+
const filled = deriveConsequences(run, mine.map(({ recall }) => recall), placeholder);
|
|
35
|
+
const promptKeys = new Set(run.prompts.map((prompt) => `${prompt.session}#${String(prompt.index)}`));
|
|
36
|
+
filled.forEach((recall, position) => {
|
|
37
|
+
const entry = mine[position];
|
|
38
|
+
const lookup = asked[position];
|
|
39
|
+
if (entry === undefined || lookup === undefined)
|
|
40
|
+
return;
|
|
41
|
+
derived[entry.at] = recall;
|
|
42
|
+
if (promptKeys.has(`${lookup.session}#${String(lookup.prompt)}`))
|
|
43
|
+
pending.push({ at: entry.at, filled: recall, ...lookup });
|
|
44
|
+
});
|
|
45
|
+
graph = recallGraphOf(run, filled);
|
|
46
|
+
cells.add(projectedStore(run, read));
|
|
47
|
+
reads.add(run);
|
|
48
|
+
};
|
|
49
|
+
const finish = () => {
|
|
50
|
+
const reverted = finishReversions(index);
|
|
51
|
+
for (const { at, filled, session, prompt } of pending) {
|
|
52
|
+
if (filled.consequence === undefined)
|
|
53
|
+
continue;
|
|
54
|
+
derived[at] = { ...filled, consequence: { ...filled.consequence, outcome: { ...filled.consequence.outcome, reverted: reverted(session, prompt) } } };
|
|
55
|
+
}
|
|
56
|
+
return { recalls: derived, cells: cells.finish(), reads: reads.finish() };
|
|
57
|
+
};
|
|
58
|
+
return { add, finish, index };
|
|
59
|
+
};
|
|
60
|
+
/** The folds of a store held whole: one run. */
|
|
61
|
+
export const poolFoldsOf = (recalls, store) => {
|
|
62
|
+
const fold = poolFold(recalls);
|
|
63
|
+
fold.add(store);
|
|
64
|
+
return fold.finish();
|
|
65
|
+
};
|
|
@@ -55,6 +55,12 @@ export declare const heldBatches: (outbox: string, stamp?: string) => Promise<{
|
|
|
55
55
|
batches: Held[];
|
|
56
56
|
edited: number;
|
|
57
57
|
}>;
|
|
58
|
+
/** A batch file of the outbox, found by its name alone: nothing of it is read yet, so a run over thousands of them holds one at a time. */
|
|
59
|
+
export type HeldName = Omit<Held, 'bytes'>;
|
|
60
|
+
/** The batch files filed under this stamp, by name, in name order (the same ones `heldBatches` reads, whether or not they still hash to their names). */
|
|
61
|
+
export declare function namesHeld(outbox: string, stamp?: string): AsyncGenerator<HeldName>;
|
|
62
|
+
/** `namesHeld` over every stamp a send reads, the current one first. */
|
|
63
|
+
export declare function namesHeldAcross(outbox: string): AsyncGenerator<HeldName>;
|
|
58
64
|
/** The stamps a send reads: the current one, then every other stamp `pulled.json` lists. A listed name that is not a stamp folder is not a path this reads. */
|
|
59
65
|
export declare const sendStamps: (outbox: string) => Promise<string[]>;
|
|
60
66
|
/** `heldBatches` over every stamp a send reads, the current one first. */
|
|
@@ -70,6 +76,9 @@ export declare const machineKeys: (outbox: string) => Promise<Set<string>>;
|
|
|
70
76
|
export declare const noteMachineKey: (outbox: string, key: string) => Promise<void>;
|
|
71
77
|
/** Records that this hash was printed, one line for each print: the lock below sends only a hash that appears here. */
|
|
72
78
|
export declare const markShown: (outbox: string, hash: string, at: number) => Promise<void>;
|
|
79
|
+
/** Every hash `shown.log` holds, read once: a run over thousands of batches must not read the file for each. */
|
|
80
|
+
export declare const shownHashes: (outbox: string) => Promise<Set<string>>;
|
|
81
|
+
export declare const wasShown: (outbox: string, hash: string) => Promise<boolean>;
|
|
73
82
|
export type LockRefusal = 'batch_unknown' | 'batch_not_shown' | 'batch_changed';
|
|
74
83
|
/**
|
|
75
84
|
* The lock on a send: a batch goes only if it is in the outbox, its hash was printed, and its file still hashes to that hash. The first check that fails names why. The detached sender (hook-bin.js nightly) sends with `requireShown: false`,
|
|
@@ -246,11 +246,22 @@ export const unchangedBytes = async (file, hash) => {
|
|
|
246
246
|
* under any other stamp it is only a batch `pulled.json` lists there, since anything else in that folder is stale and is never printed or sent.
|
|
247
247
|
*/
|
|
248
248
|
export const heldBatches = async (outbox, stamp = currentStamp()) => {
|
|
249
|
+
const batches = [];
|
|
250
|
+
let edited = 0;
|
|
251
|
+
for await (const named of namesHeld(outbox, stamp)) {
|
|
252
|
+
const bytes = await unchangedBytes(named.file, named.hash);
|
|
253
|
+
if (bytes === undefined)
|
|
254
|
+
edited += 1;
|
|
255
|
+
else
|
|
256
|
+
batches.push({ ...named, bytes });
|
|
257
|
+
}
|
|
258
|
+
return { batches, edited };
|
|
259
|
+
};
|
|
260
|
+
/** The batch files filed under this stamp, by name, in name order (the same ones `heldBatches` reads, whether or not they still hash to their names). */
|
|
261
|
+
export async function* namesHeld(outbox, stamp = currentStamp()) {
|
|
249
262
|
const folder = join(outbox, stamp);
|
|
250
263
|
const listed = await pulledAt(outbox, stamp);
|
|
251
264
|
const here = stamp === currentStamp();
|
|
252
|
-
const batches = [];
|
|
253
|
-
let edited = 0;
|
|
254
265
|
for (const name of await namesIn(folder)) {
|
|
255
266
|
const found = BATCH_FILE.exec(name);
|
|
256
267
|
if (found === null)
|
|
@@ -258,15 +269,14 @@ export const heldBatches = async (outbox, stamp = currentStamp()) => {
|
|
|
258
269
|
const [, key = '', hash = ''] = found;
|
|
259
270
|
if (!here && !listed.has(key))
|
|
260
271
|
continue;
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
};
|
|
272
|
+
yield { key, stamp, pulled: listed.has(key), hash, file: join(folder, name) };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/** `namesHeld` over every stamp a send reads, the current one first. */
|
|
276
|
+
export async function* namesHeldAcross(outbox) {
|
|
277
|
+
for (const stamp of await sendStamps(outbox))
|
|
278
|
+
yield* namesHeld(outbox, stamp);
|
|
279
|
+
}
|
|
270
280
|
/** The stamps a send reads: the current one, then every other stamp `pulled.json` lists. A listed name that is not a stamp folder is not a path this reads. */
|
|
271
281
|
export const sendStamps = async (outbox) => {
|
|
272
282
|
const others = new Set(Object.values(await readPulled(outbox, POOL_STEP)).filter((stamp) => stamp !== currentStamp() && STAMP_FOLDER.test(stamp)));
|
|
@@ -323,7 +333,18 @@ export const markShown = async (outbox, hash, at) => {
|
|
|
323
333
|
await mkdirOwnerOnly(outbox);
|
|
324
334
|
await appendOwnerOnly(shownFile(outbox), `${hash} ${new Date(at).toISOString()}\n`);
|
|
325
335
|
};
|
|
326
|
-
|
|
336
|
+
/** Every hash `shown.log` holds, read once: a run over thousands of batches must not read the file for each. */
|
|
337
|
+
export const shownHashes = async (outbox) => {
|
|
338
|
+
try {
|
|
339
|
+
return new Set((await readFile(shownFile(outbox), 'utf8')).split('\n').map((line) => line.split(' ')[0] ?? '').filter((hash) => hash !== ''));
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
if (isMissing(error))
|
|
343
|
+
return new Set();
|
|
344
|
+
throw error;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
export const wasShown = async (outbox, hash) => {
|
|
327
348
|
try {
|
|
328
349
|
return (await readFile(shownFile(outbox), 'utf8')).split('\n').some((line) => line.split(' ')[0] === hash);
|
|
329
350
|
}
|
package/dist/sessions/pool.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type Pool } from './cells.js';
|
|
2
2
|
import { type Read } from './read.js';
|
|
3
|
+
import { type PoolFolds } from './pool-fold.js';
|
|
4
|
+
import type { ReadFolds } from './read-folds.js';
|
|
3
5
|
import { type Store } from './store.js';
|
|
4
6
|
export declare const poolFiles: (storeDir: string) => {
|
|
5
7
|
cells: string;
|
|
@@ -8,17 +10,20 @@ export declare const poolFiles: (storeDir: string) => {
|
|
|
8
10
|
reads: (version: string) => string;
|
|
9
11
|
};
|
|
10
12
|
/** Every family's order 1 reads of one pool, in family order and then by slice, so a rerun writes the same bytes. */
|
|
11
|
-
export declare const deriveFirstOrder: (pool: Pool, store: Store) => Read[];
|
|
13
|
+
export declare const deriveFirstOrder: (pool: Pool, store: Store, folds?: ReadFolds) => Read[];
|
|
12
14
|
/** Order 1, then each stage of `relations` over everything before it. */
|
|
13
|
-
export declare const deriveReads: (pool: Pool, store: Store) => Read[];
|
|
15
|
+
export declare const deriveReads: (pool: Pool, store: Store, folds?: ReadFolds) => Read[];
|
|
14
16
|
export declare const serializeReads: (reads: readonly Read[]) => string;
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The current pool's reads: already written when this pool has been read before, else derived and written once. `store` holds the sessions, subagents, and errors; the rows of every turn are read through
|
|
19
|
+
* `folds`, which a refresh folds run by run. Without them `store` is taken whole, with its prompts and tools, and folded as one run.
|
|
20
|
+
*/
|
|
21
|
+
export declare const refreshPool: (storeDir: string, store: Store, folds?: PoolFolds) => Promise<{
|
|
17
22
|
pool: Pool;
|
|
18
23
|
reads: Read[];
|
|
19
24
|
}>;
|
|
20
25
|
/** The pool a deposit's index step reads. It answers without one (spec 2.7): a pool that cannot be read or written costs only the stored read IDs, whose answers keep their stand-ins, and one line on stderr says so. */
|
|
21
|
-
export declare const currentPool: (storeDir: string, store: Store) => Promise<{
|
|
26
|
+
export declare const currentPool: (storeDir: string, store: Store, folds?: PoolFolds | Error) => Promise<{
|
|
22
27
|
pool: Pool;
|
|
23
28
|
reads: Read[];
|
|
24
29
|
} | undefined>;
|
package/dist/sessions/pool.js
CHANGED
|
@@ -4,12 +4,9 @@
|
|
|
4
4
|
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
5
5
|
import { basename, dirname, join } from 'node:path';
|
|
6
6
|
import { CliError, ExitCode } from '../errors.js';
|
|
7
|
-
import { cellsOf, projectedStore } from './cell-increments.js';
|
|
8
7
|
import { poolOf, serializeCells, UNKNOWN } from './cells.js';
|
|
9
8
|
import { LOAD_STATUS_FILE, loadStatusLines } from './load-status.js';
|
|
10
|
-
import { shellReader } from './projection.js';
|
|
11
9
|
import { contentHash, FIRST_ORDER_FAMILIES, frozenRead, makeRead, orderResolver } from './read.js';
|
|
12
|
-
import { deriveConsequences, recallGraphOf } from './recall-consequence.js';
|
|
13
10
|
import { readRecalls } from './recall-object.js';
|
|
14
11
|
import { deriveAdoption } from './read-adoption.js';
|
|
15
12
|
import { deriveCallShape } from './read-call-shape.js';
|
|
@@ -21,7 +18,7 @@ import { deriveMethod } from './read-method.js';
|
|
|
21
18
|
import { deriveNextStep } from './read-next-step.js';
|
|
22
19
|
import { deriveRecovery } from './read-recovery.js';
|
|
23
20
|
import { deriveToolDay } from './read-tool-day.js';
|
|
24
|
-
import {
|
|
21
|
+
import { poolFoldsOf } from './pool-fold.js';
|
|
25
22
|
import { deriveDayMove } from './read-day-move.js';
|
|
26
23
|
import { deriveInteraction } from './read-interaction.js';
|
|
27
24
|
import { deriveLeadLag } from './read-lead-lag.js';
|
|
@@ -54,19 +51,19 @@ const made = (bodies, asOf, orderOf) => bodies
|
|
|
54
51
|
.sort((a, b) => (a.key < b.key ? -1 : 1))
|
|
55
52
|
.map(({ read }) => read);
|
|
56
53
|
/** Every family's order 1 reads of one pool, in family order and then by slice, so a rerun writes the same bytes. */
|
|
57
|
-
export const deriveFirstOrder = (pool, store) => {
|
|
54
|
+
export const deriveFirstOrder = (pool, store, folds) => {
|
|
58
55
|
const asOf = { pool: pool.version, day: pool.lastDay };
|
|
59
56
|
const orderOf = orderResolver([]);
|
|
60
57
|
const byFamily = derivers();
|
|
61
|
-
return FIRST_ORDER_FAMILIES.flatMap((family) => made(byFamily[family]({ pool, store }), asOf, orderOf));
|
|
58
|
+
return FIRST_ORDER_FAMILIES.flatMap((family) => made(byFamily[family]({ pool, store, ...(folds === undefined ? {} : { folds }) }), asOf, orderOf));
|
|
62
59
|
};
|
|
63
60
|
/** Order 1, then each stage of `relations` over everything before it. */
|
|
64
|
-
export const deriveReads = (pool, store) => {
|
|
61
|
+
export const deriveReads = (pool, store, folds) => {
|
|
65
62
|
const asOf = { pool: pool.version, day: pool.lastDay };
|
|
66
63
|
return relations().reduce((known, stage) => {
|
|
67
64
|
const orderOf = orderResolver(known);
|
|
68
65
|
return [...known, ...stage.flatMap((derive) => made(derive(known), asOf, orderOf))];
|
|
69
|
-
}, deriveFirstOrder(pool, store));
|
|
66
|
+
}, deriveFirstOrder(pool, store, folds));
|
|
70
67
|
};
|
|
71
68
|
export const serializeReads = (reads) => reads.map((read) => `${JSON.stringify(read)}\n`).join('');
|
|
72
69
|
const isRead = (value) => typeof value === 'object' && value !== null && 'readId' in value && 'family' in value;
|
|
@@ -94,10 +91,12 @@ const storedRows = async (file, isRow) => {
|
|
|
94
91
|
}
|
|
95
92
|
};
|
|
96
93
|
const readStored = async (file) => (await storedRows(file, isRead))?.map(frozenRead);
|
|
97
|
-
/**
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
94
|
+
/**
|
|
95
|
+
* The current pool's reads: already written when this pool has been read before, else derived and written once. `store` holds the sessions, subagents, and errors; the rows of every turn are read through
|
|
96
|
+
* `folds`, which a refresh folds run by run. Without them `store` is taken whole, with its prompts and tools, and folded as one run.
|
|
97
|
+
*/
|
|
98
|
+
export const refreshPool = async (storeDir, store, folds) => {
|
|
99
|
+
const { recalls, cells: set, reads: readFolds } = folds ?? poolFoldsOf(await readRecalls(storeDir), store);
|
|
101
100
|
const pool = poolOf(set, store.errors);
|
|
102
101
|
const files = poolFiles(storeDir);
|
|
103
102
|
await writeAtomic(files.consequences, recalls.flatMap(({ id, n, consequence }) => (consequence === undefined ? [] : [`${JSON.stringify({ id, n, consequence })}\n`])).join(''));
|
|
@@ -108,7 +107,7 @@ export const refreshPool = async (storeDir, store) => {
|
|
|
108
107
|
const stored = await readStored(file);
|
|
109
108
|
if (stored !== undefined)
|
|
110
109
|
return { pool, reads: stored };
|
|
111
|
-
const reads = deriveReads(pool, store);
|
|
110
|
+
const reads = deriveReads(pool, store, readFolds);
|
|
112
111
|
await writeAtomic(file, serializeReads(reads));
|
|
113
112
|
return { pool, reads };
|
|
114
113
|
};
|
|
@@ -117,9 +116,11 @@ const unbuilt = (error) => {
|
|
|
117
116
|
process.stderr.write(`${POOL_FAILED}${error instanceof Error ? error.message : String(error)}\n`);
|
|
118
117
|
};
|
|
119
118
|
/** The pool a deposit's index step reads. It answers without one (spec 2.7): a pool that cannot be read or written costs only the stored read IDs, whose answers keep their stand-ins, and one line on stderr says so. */
|
|
120
|
-
export const currentPool = async (storeDir, store) => {
|
|
119
|
+
export const currentPool = async (storeDir, store, folds) => {
|
|
121
120
|
try {
|
|
122
|
-
|
|
121
|
+
if (folds instanceof Error)
|
|
122
|
+
throw folds;
|
|
123
|
+
return await refreshPool(storeDir, store, folds);
|
|
123
124
|
}
|
|
124
125
|
catch (error) {
|
|
125
126
|
unbuilt(error);
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
import { lastDayOfCell, parseShapeClass, UNKNOWN } from './cells.js';
|
|
6
6
|
import { OPEN_GATE, wireKeys } from './pool-wire.js';
|
|
7
7
|
import { CALL_SHAPE_METHOD } from './read.js';
|
|
8
|
-
import { actionOfShape,
|
|
8
|
+
import { actionOfShape, toolShapeId } from './recall-reads.js';
|
|
9
|
+
import { readFoldsOf } from './read-folds.js';
|
|
9
10
|
import { body, atGrain, rate, wilson } from './read-support.js';
|
|
10
|
-
export const deriveCallShape = ({ pool, store }) =>
|
|
11
|
+
export const deriveCallShape = ({ pool, store, folds }) => (folds ?? readFoldsOf(store)).fit.flatMap((read) => {
|
|
11
12
|
const action = actionOfShape(read.tool, read.harnesses);
|
|
12
13
|
// The cell names the keys as the wire sends them (`projectedStore`); a record captured before steps was never projected, so its raw names are the other spelling.
|
|
13
14
|
const names = new Set([wireKeys(read.keys, read.tool, OPEN_GATE).keys.join(','), read.keys.join(',')]);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type RecallRead } from './recall-reads.js';
|
|
2
|
+
import type { PromptObservation } from './schema.js';
|
|
3
|
+
import type { Store } from './store.js';
|
|
4
|
+
export interface ReadFolds {
|
|
5
|
+
/** `fitReads` over the whole store. */
|
|
6
|
+
fit: RecallRead[];
|
|
7
|
+
/** The hook index's fit reads: `fitReads` at the push floors, kept above the base rate of their action. */
|
|
8
|
+
push: RecallRead[];
|
|
9
|
+
/** The frame day of each prompt an error of a main session names, by `session#index`. */
|
|
10
|
+
promptDays: ReadonlyMap<string, string>;
|
|
11
|
+
}
|
|
12
|
+
/** The day a prompt's frame was written under: its frame's, else the day of its stamp. */
|
|
13
|
+
export declare const promptDayOf: (prompt: PromptObservation) => string;
|
|
14
|
+
export interface ReadFold {
|
|
15
|
+
/** One run of sessions; its errors name prompts of its own sessions only. */
|
|
16
|
+
add: (run: Store) => void;
|
|
17
|
+
finish: () => ReadFolds;
|
|
18
|
+
}
|
|
19
|
+
export declare const readFold: () => ReadFold;
|
|
20
|
+
/** The folds of a store held whole: one run. */
|
|
21
|
+
export declare const readFoldsOf: (store: Store) => ReadFolds;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// What the two families that read the rows of every turn keep of them (t1243): the fit shapes `deriveCallShape` cites, and the days of the prompts a recovery read's errors name. A refresh folds
|
|
2
|
+
// them one run of sessions at a time, so neither family holds the tools or prompts of the whole history; a store held whole folds the same way as one run.
|
|
3
|
+
import { UNKNOWN } from './cells.js';
|
|
4
|
+
import { utcDay } from './frame.js';
|
|
5
|
+
import { actionFold, aboveActionRate, fitFold, PUSH_FLOORS } from './recall-reads.js';
|
|
6
|
+
/** The day a prompt's frame was written under: its frame's, else the day of its stamp. */
|
|
7
|
+
export const promptDayOf = (prompt) => prompt.frame?.day ?? utcDay(prompt.ts) ?? UNKNOWN;
|
|
8
|
+
export const readFold = () => {
|
|
9
|
+
const fit = fitFold();
|
|
10
|
+
const pushFit = fitFold(PUSH_FLOORS);
|
|
11
|
+
const actions = actionFold();
|
|
12
|
+
const promptDays = new Map();
|
|
13
|
+
return {
|
|
14
|
+
add: (run) => {
|
|
15
|
+
fit.add(run);
|
|
16
|
+
pushFit.add(run);
|
|
17
|
+
actions.add(run);
|
|
18
|
+
const prompts = new Map(run.prompts.map((prompt) => [`${prompt.session}#${String(prompt.index)}`, prompt]));
|
|
19
|
+
for (const error of run.errors) {
|
|
20
|
+
// A subagent's error is filed under its own session's frame, never a prompt's.
|
|
21
|
+
if (error.agent !== undefined)
|
|
22
|
+
continue;
|
|
23
|
+
const key = `${error.session}#${String(error.prompt)}`;
|
|
24
|
+
const prompt = prompts.get(key);
|
|
25
|
+
if (prompt !== undefined)
|
|
26
|
+
promptDays.set(key, promptDayOf(prompt));
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
finish: () => ({ fit: fit.finish(), push: aboveActionRate(actions.finish(), pushFit.finish()), promptDays }),
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
/** The folds of a store held whole: one run. */
|
|
33
|
+
export const readFoldsOf = (store) => {
|
|
34
|
+
const fold = readFold();
|
|
35
|
+
fold.add(store);
|
|
36
|
+
return fold.finish();
|
|
37
|
+
};
|
|
@@ -16,5 +16,5 @@ export interface RecoveryOutcome {
|
|
|
16
16
|
[reason in RecoveryDrop]: number;
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
-
export declare const recoveryOf: ({ pool, store }: FamilyInput) => RecoveryOutcome;
|
|
19
|
+
export declare const recoveryOf: ({ pool, store, folds }: FamilyInput) => RecoveryOutcome;
|
|
20
20
|
export declare const deriveRecovery: DeriveFamily;
|
|
@@ -6,6 +6,7 @@ import { UNKNOWN, weekStartOf } from './cells.js';
|
|
|
6
6
|
import { utcDay } from './frame.js';
|
|
7
7
|
import { shapeMotif } from './motif.js';
|
|
8
8
|
import { contentHash } from './read.js';
|
|
9
|
+
import { readFoldsOf } from './read-folds.js';
|
|
9
10
|
import { atGrain, body, groupBy } from './read-support.js';
|
|
10
11
|
import { harnessOf } from './schema.js';
|
|
11
12
|
import { signatureRecord } from './store.js';
|
|
@@ -35,9 +36,9 @@ const oneRowPerCall = (errors) => {
|
|
|
35
36
|
}
|
|
36
37
|
return [...rows, ...calls.values()];
|
|
37
38
|
};
|
|
38
|
-
export const recoveryOf = ({ pool, store }) => {
|
|
39
|
+
export const recoveryOf = ({ pool, store, folds }) => {
|
|
39
40
|
const sessions = new Map([...store.sessions, ...store.subagents].map((session) => [session.session, session]));
|
|
40
|
-
const
|
|
41
|
+
const promptDays = (folds ?? readFoldsOf(store)).promptDays;
|
|
41
42
|
const actionIn = (session, tool) => actionOf(ACTION_TABLES[harnessOf(sessions.get(session) ?? {})], tool).action;
|
|
42
43
|
// The failed call is a one-step shape, which has a live day cell. The step after it is a two-step window, which has a week cell only (rule 6), so it is found on the week its day falls in.
|
|
43
44
|
const cellsAt = groupBy(atGrain(pool, 'day'), (cell) => `${cell.motifId}/${cell.day}`);
|
|
@@ -45,9 +46,9 @@ export const recoveryOf = ({ pool, store }) => {
|
|
|
45
46
|
const cellsOf = groupBy(atGrain(pool, 'day'), (cell) => cell.motifId);
|
|
46
47
|
/** The day a cell files this error's call under: its frame's, which is its prompt's, or its session's for a subagent or a call before the first prompt. An error can run past that midnight. */
|
|
47
48
|
const frameDayOf = (error) => {
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
50
|
-
return
|
|
49
|
+
const day = error.agent === undefined ? promptDays.get(`${error.session}#${String(error.prompt)}`) : undefined;
|
|
50
|
+
if (day !== undefined)
|
|
51
|
+
return day;
|
|
51
52
|
const session = sessions.get(error.agent ?? error.session);
|
|
52
53
|
return session?.frame?.day ?? utcDay(session?.tsFirst) ?? UNKNOWN;
|
|
53
54
|
};
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { type Cell, type Grain, type Pool } from './cells.js';
|
|
2
2
|
import type { Motif } from './motif.js';
|
|
3
3
|
import { type Family, type ReadDraft, type ReadInterval, type ReadSlice, type ReadValue } from './read.js';
|
|
4
|
+
import type { ReadFolds } from './read-folds.js';
|
|
4
5
|
import type { Store } from './store.js';
|
|
5
6
|
export interface FamilyInput {
|
|
6
7
|
pool: Pool;
|
|
7
|
-
/** Only recovery reads this: an error's signature is not in a cell. */
|
|
8
|
+
/** Its sessions, subagents, and errors. Only recovery reads this: an error's signature is not in a cell. A refresh holds no tools or prompts here: the two families that read them take `folds`. */
|
|
8
9
|
store: Store;
|
|
10
|
+
/** What a refresh folded from the rows of every turn, run by run; absent, the two families that need it fold `store` whole. */
|
|
11
|
+
folds?: ReadFolds;
|
|
9
12
|
}
|
|
10
13
|
/** A read as a family states it; the pool's snapshot is added to all of them in one place. */
|
|
11
14
|
export type ReadBody = Omit<ReadDraft, 'asOf'>;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type CountedStep } from './motif.js';
|
|
2
2
|
import type { Recall } from './recall-object.js';
|
|
3
|
+
import { reversionsOf } from './reversion.js';
|
|
3
4
|
import { type ToolObservation } from './schema.js';
|
|
4
5
|
import type { Store } from './store.js';
|
|
5
6
|
/** A consequence window ends at the prompt's end or after this many calls (spec Section 2.7). */
|
|
6
7
|
export declare const WINDOW_CALLS = 20;
|
|
7
8
|
/** Fills `n` and `consequence`. A recall with no session, or from a session this store does not hold, has nothing to read and comes back as it was. */
|
|
8
|
-
export declare const deriveConsequences: (store: Store, recalls: readonly Recall[]) => Recall[];
|
|
9
|
+
export declare const deriveConsequences: (store: Store, recalls: readonly Recall[], reverted?: ReturnType<typeof reversionsOf>) => Recall[];
|
|
9
10
|
/** A step as a cell counts it, and whether it sits inside a shown recall's window. */
|
|
10
11
|
export interface PlacedStep {
|
|
11
12
|
step: CountedStep;
|
|
@@ -74,11 +74,10 @@ const consequenceOf = (recall, session, line, position, harness, prompts, revert
|
|
|
74
74
|
};
|
|
75
75
|
};
|
|
76
76
|
/** Fills `n` and `consequence`. A recall with no session, or from a session this store does not hold, has nothing to read and comes back as it was. */
|
|
77
|
-
export const deriveConsequences = (store, recalls) => {
|
|
77
|
+
export const deriveConsequences = (store, recalls, reverted = reversionsOf(store)) => {
|
|
78
78
|
const lines = mainLines(store);
|
|
79
79
|
const prompts = new Map(store.prompts.map((prompt) => [promptKey(prompt.session, prompt.index), prompt]));
|
|
80
80
|
const sessions = new Map(store.sessions.map((session) => [session.session, session]));
|
|
81
|
-
const reverted = reversionsOf(store);
|
|
82
81
|
return recalls.map((recall) => {
|
|
83
82
|
const session = recall.sid === null ? undefined : sessions.get(recall.sid);
|
|
84
83
|
if (session === undefined)
|
|
@@ -53,7 +53,16 @@ export declare const FIT_FLOORS: FitFloors;
|
|
|
53
53
|
* `FIT_FLOORS`: a base rate is still a fact to read on request.
|
|
54
54
|
*/
|
|
55
55
|
export declare const PUSH_FLOORS: FitFloors;
|
|
56
|
-
|
|
56
|
+
export interface FitFold {
|
|
57
|
+
add: (store: Store) => void;
|
|
58
|
+
finish: () => RecallRead[];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* `fitReads` as a fold: each `add` tallies one run of sessions into the same shapes, and `finish` applies the floors. Runs come in the whole store's row order, so the tie order of `followed` matches.
|
|
62
|
+
* `keep` narrows which calls are tallied, not what can come next: the call after a kept one is what followed it, wherever the slice ends.
|
|
63
|
+
*/
|
|
64
|
+
export declare const fitFold: (floors?: FitFloors, keep?: (call: ToolObservation) => boolean) => FitFold;
|
|
65
|
+
/** `fitFold` over the whole store at once. `keep` narrows the calls tallied, not what follows them. */
|
|
57
66
|
export declare const fitReads: (store: Store, floors?: FitFloors, keep?: (call: ToolObservation) => boolean) => RecallRead[];
|
|
58
67
|
/** The action of a tool under the first of its harnesses whose table lists it, else under the first harness (the default one when a read names none). */
|
|
59
68
|
export declare const actionOfShape: (tool: string, harnesses: readonly string[]) => CanonicalAction;
|
|
@@ -63,6 +72,19 @@ export declare const actionOfShape: (tool: string, harnesses: readonly string[])
|
|
|
63
72
|
* 79 of 79 pushes, at 4.5x and 8.6x their action's share: a real signal, not a defect. Open parameter, like the floors.
|
|
64
73
|
*/
|
|
65
74
|
export declare const PUSH_MIN_LIFT = 2;
|
|
75
|
+
/** The per-action call tally `aboveBaseRate` compares against, as a fold: each `add` tallies one run of sessions, so a refresh never holds every call. */
|
|
76
|
+
export declare const actionFold: () => {
|
|
77
|
+
add: (store: Store) => void;
|
|
78
|
+
finish: () => Map<CanonicalAction, {
|
|
79
|
+
calls: number;
|
|
80
|
+
failed: number;
|
|
81
|
+
}>;
|
|
82
|
+
};
|
|
83
|
+
/** `aboveBaseRate` over a finished action tally. */
|
|
84
|
+
export declare const aboveActionRate: (byAction: ReadonlyMap<CanonicalAction, {
|
|
85
|
+
calls: number;
|
|
86
|
+
failed: number;
|
|
87
|
+
}>, reads: readonly RecallRead[], lift?: number) => RecallRead[];
|
|
66
88
|
/**
|
|
67
89
|
* The reads whose shape fails at `lift` times the share of its whole action or more, where the action's share counts every call of every shape of it, those under the floors too.
|
|
68
90
|
* A tool no table lists is the action `other` (or `mcp.call`), so it is held to that action's share like any other and can enter the index and fire. Compared in integers, so a tie is exact.
|