@hviana/sema 0.5.8 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/AGENTS.md +43 -4
  2. package/DATASETS.md +160 -0
  3. package/README.md +12 -0
  4. package/dist/example/train_base/cache.d.ts +35 -0
  5. package/dist/example/train_base/cache.js +211 -0
  6. package/dist/example/train_base/config.d.ts +21 -0
  7. package/dist/example/train_base/config.js +94 -0
  8. package/dist/example/train_base/corpora/aya.d.ts +19 -0
  9. package/dist/example/train_base/corpora/aya.js +76 -0
  10. package/dist/example/train_base/corpora/converted-parquet.d.ts +14 -0
  11. package/dist/example/train_base/corpora/converted-parquet.js +44 -0
  12. package/dist/example/train_base/corpora/genknow.d.ts +14 -0
  13. package/dist/example/train_base/corpora/genknow.js +83 -0
  14. package/dist/example/train_base/corpora/index.d.ts +29 -0
  15. package/dist/example/train_base/corpora/index.js +81 -0
  16. package/dist/example/train_base/corpora/massive.d.ts +7 -0
  17. package/dist/example/train_base/corpora/massive.js +98 -0
  18. package/dist/example/train_base/corpora/oasst2.d.ts +52 -0
  19. package/dist/example/train_base/corpora/oasst2.js +120 -0
  20. package/dist/example/train_base/corpora/smolsent.d.ts +23 -0
  21. package/dist/example/train_base/corpora/smolsent.js +156 -0
  22. package/dist/example/train_base/corpora/soda.d.ts +12 -0
  23. package/dist/example/train_base/corpora/soda.js +113 -0
  24. package/dist/example/train_base/corpora/taskmaster.d.ts +15 -0
  25. package/dist/example/train_base/corpora/taskmaster.js +144 -0
  26. package/dist/example/train_base/corpora/wiki2.d.ts +23 -0
  27. package/dist/example/train_base/corpora/wiki2.js +132 -0
  28. package/dist/example/train_base/corpus.d.ts +88 -0
  29. package/dist/example/train_base/corpus.js +65 -0
  30. package/dist/example/train_base/discovery.d.ts +48 -0
  31. package/dist/example/train_base/discovery.js +143 -0
  32. package/dist/example/train_base/http.d.ts +82 -0
  33. package/dist/example/train_base/http.js +219 -0
  34. package/dist/example/train_base/items.d.ts +46 -0
  35. package/dist/example/train_base/items.js +98 -0
  36. package/dist/example/train_base/main.d.ts +4 -0
  37. package/dist/example/train_base/main.js +207 -0
  38. package/dist/example/train_base/progress.d.ts +34 -0
  39. package/dist/example/train_base/progress.js +114 -0
  40. package/dist/example/train_base/readers.d.ts +125 -0
  41. package/dist/example/train_base/readers.js +391 -0
  42. package/dist/example/train_base/runtime.d.ts +115 -0
  43. package/dist/example/train_base/runtime.js +637 -0
  44. package/dist/example/train_base/stage.d.ts +3 -0
  45. package/dist/example/train_base/stage.js +246 -0
  46. package/dist/example/train_base/ui.d.ts +88 -0
  47. package/dist/example/train_base/ui.js +272 -0
  48. package/dist/src/geometry.d.ts +20 -0
  49. package/dist/src/geometry.js +22 -0
  50. package/dist/src/mind/attention.d.ts +6 -0
  51. package/dist/src/mind/attention.js +44 -4
  52. package/dist/src/mind/learning.js +134 -50
  53. package/dist/src/mind/mechanisms/cast.js +45 -1
  54. package/dist/src/mind/mind.d.ts +7 -2
  55. package/dist/src/mind/mind.js +15 -3
  56. package/dist/src/mind/reasoning.js +59 -5
  57. package/dist/src/mind/recognition.js +29 -3
  58. package/dist/src/mind/traverse.d.ts +16 -0
  59. package/dist/src/mind/traverse.js +18 -0
  60. package/dist/src/store-sqlite.d.ts +4 -0
  61. package/dist/src/store-sqlite.js +47 -0
  62. package/dist/src/store.d.ts +7 -0
  63. package/example/train_base/cache.ts +251 -0
  64. package/example/train_base/config.ts +128 -0
  65. package/example/train_base/corpora/aya.ts +106 -0
  66. package/example/train_base/corpora/converted-parquet.ts +64 -0
  67. package/example/train_base/corpora/genknow.ts +114 -0
  68. package/example/train_base/corpora/index.ts +88 -0
  69. package/example/train_base/corpora/massive.ts +111 -0
  70. package/example/train_base/corpora/oasst2.ts +163 -0
  71. package/example/train_base/corpora/smolsent.ts +203 -0
  72. package/example/train_base/corpora/soda.ts +130 -0
  73. package/example/train_base/corpora/taskmaster.ts +217 -0
  74. package/example/train_base/corpora/wiki2.ts +190 -0
  75. package/example/train_base/corpus.ts +150 -0
  76. package/example/train_base/discovery.ts +203 -0
  77. package/example/train_base/http.ts +284 -0
  78. package/example/train_base/items.ts +118 -0
  79. package/example/train_base/main.ts +240 -0
  80. package/example/train_base/progress.ts +149 -0
  81. package/example/train_base/readers.ts +505 -0
  82. package/example/train_base/runtime.ts +894 -0
  83. package/example/train_base/stage.ts +276 -0
  84. package/example/train_base/ui.ts +333 -0
  85. package/jsr.json +1 -1
  86. package/package.json +2 -4
  87. package/src/geometry.ts +23 -0
  88. package/src/mind/attention.ts +54 -1
  89. package/src/mind/learning.ts +137 -43
  90. package/src/mind/mechanisms/cast.ts +48 -1
  91. package/src/mind/mind.ts +13 -2
  92. package/src/mind/reasoning.ts +64 -5
  93. package/src/mind/recognition.ts +29 -3
  94. package/src/mind/traverse.ts +19 -0
  95. package/src/store-sqlite.ts +53 -0
  96. package/src/store.ts +28 -0
  97. package/test/13-conversation.test.mjs +1 -1
  98. package/test/29-counterfactual.test.mjs +43 -6
  99. package/test/77-company-saturation.test.mjs +302 -0
  100. package/test/78-atom-hub-recognition-cliff.test.mjs +135 -0
  101. package/test/84-composed-answer-honesty.test.mjs +137 -0
  102. package/test/85-answered-directly.test.mjs +126 -0
  103. package/test/86-cast-voices-committed.test.mjs +164 -0
  104. package/test/87-codominant-commitment.test.mjs +250 -0
  105. package/test/88-dependency-footprint.test.mjs +99 -0
  106. package/dist/example/train_base.d.ts +0 -93
  107. package/dist/example/train_base.js +0 -2269
  108. package/example/train_base.ts +0 -2735
@@ -0,0 +1,76 @@
1
+ // train_base/corpora/aya.ts — CohereLabs/aya_dataset prompt→completion pairs
2
+ //
3
+ // Knobs, the row adapter, and the stage descriptor for ONE corpus, together
4
+ // with the evidence that fixed each default. A corpus file owns everything
5
+ // source-specific; the contract it fills is ../corpus.ts and the loop that runs
6
+ // it is ../stage.ts.
7
+ import { env } from "../config.js";
8
+ import { refineItems } from "../items.js";
9
+ import { parquet } from "../readers.js";
10
+ import { singleUnit } from "../corpus.js";
11
+ // ── CohereLabs/aya_dataset (the second training stage, after SmolSent) ──
12
+ // The Aya Dataset is ~204k HUMAN-annotated prompt→completion pairs across 70+
13
+ // languages, each a clean (inputs → targets) fact in a named language. It ships
14
+ // ONLY as Snappy-compressed Parquet (no JSONL/CSV). We DOWNLOAD the one train
15
+ // Parquet file and read it row-group by row-group with `hyparquet` (a pure-JS,
16
+ // dependency-free Parquet reader) + `hyparquet-compressors` (Snappy) over a
17
+ // web-standard Blob byte source — no whole-file-in-memory load. AYA=0 disables
18
+ // the stage; AYA_URL overrides the Parquet source.
19
+ const AYA = env("AYA", "1") !== "0";
20
+ const AYA_URL = env("AYA_URL", "https://huggingface.co/datasets/CohereLabs/aya_dataset/resolve/main/data/train-00000-of-00001.parquet");
21
+ // A single Aya field this many chars or longer is skipped: inputs/targets range
22
+ // up to ~3.3M chars, and a multi-MB "pair" is documentation/dump noise, not a
23
+ // cognitive example.
24
+ const MAX_AYA_FIELD_CHARS = Math.max(10_000, Math.floor(Number(env("MAX_AYA_FIELD_KB", "256")) * 1000) || 256_000);
25
+ /** Normalize a raw datasets-server row object into an AyaRow, or null when it
26
+ * lacks a usable prompt/answer or a field is implausibly large (a dump, not a
27
+ * cognitive example). Trims surrounding whitespace; keeps inner text verbatim
28
+ * (human prose, possibly multi-paragraph). */
29
+ export function toAyaRow(row, maxChars = MAX_AYA_FIELD_CHARS) {
30
+ if (!row || typeof row !== "object")
31
+ return null;
32
+ const r = row;
33
+ const inputs = typeof r.inputs === "string" ? r.inputs.trim() : "";
34
+ const targets = typeof r.targets === "string" ? r.targets.trim() : "";
35
+ if (!inputs || !targets)
36
+ return null;
37
+ if (inputs.length > maxChars || targets.length > maxChars)
38
+ return null;
39
+ const language = typeof r.language === "string" ? r.language.trim() : "";
40
+ return { inputs, targets, language };
41
+ }
42
+ /** Translate ONE Aya row into SEMA training items. A row is a single human
43
+ * (question → answer) exchange — exactly one FACT, the (inputs → targets) edge.
44
+ * No standalone-answer experience and no one-exchange "cumulative" walk: a lone
45
+ * Q→A is not multi-turn, and both would only replicate the same edge. */
46
+ export function ayaRowToItems(row) {
47
+ const { inputs, targets } = row;
48
+ return refineItems([{ context: inputs, continuation: targets }]);
49
+ }
50
+ export const aya = {
51
+ id: "aya",
52
+ label: "Aya Dataset",
53
+ kind: "multilingual chat",
54
+ enabled: AYA,
55
+ // The three columns toAyaRow reads, out of six. Kept for the same reason as
56
+ // the other Parquet stages — the read states what the adapter uses — though
57
+ // here it is nearly free rather than a saving: measured on the train file,
58
+ // 238 MB uncompressed across all six and 233 MB for these three (98.2%). The
59
+ // dropped columns are ids and annotation metadata, so there is little to
60
+ // drop.
61
+ read: parquet({ columns: ["inputs", "targets", "language"] }),
62
+ toItems: (row) => {
63
+ const r = toAyaRow(row);
64
+ return r ? ayaRowToItems(r) : null;
65
+ },
66
+ discover: singleUnit({
67
+ // Resume id "aya::dataset" — the string this store already records.
68
+ key: "dataset",
69
+ label: "Aya Dataset",
70
+ display: "Aya Dataset",
71
+ url: AYA_URL,
72
+ dest: "aya_train.parquet",
73
+ localMatch: [/aya.*\.parquet$/i, /\.parquet$/i],
74
+ localWhat: "Aya *.parquet",
75
+ }),
76
+ };
@@ -0,0 +1,14 @@
1
+ import { type Unit } from "../corpus.js";
2
+ import type { TrainCtx } from "../runtime.js";
3
+ /** The work-list of a corpus read from Hugging Face's auto-converted
4
+ * `refs/convert/parquet` branch. Shared by 2Wiki, SODA and MASSIVE: they
5
+ * differ in their dataset, their budget and their adapter — nothing else. */
6
+ export declare function convertedParquetUnits(opts: {
7
+ id: string;
8
+ label: string;
9
+ dataset: string;
10
+ config: string;
11
+ splits: string[];
12
+ /** Subdirectory of LOCAL_PATH holding pre-downloaded shards. */
13
+ localSub: string;
14
+ }): (ctx: TrainCtx) => Promise<Unit[] | null>;
@@ -0,0 +1,44 @@
1
+ // train_base/corpora/converted-parquet.ts — the work-list shape three corpora
2
+ // share.
3
+ //
4
+ // Read from Hugging Face's auto-converted `refs/convert/parquet` branch, not
5
+ // from main: a dataset's own Parquet may be written as ONE giant row-group
6
+ // (SODA's is 1,191,582 rows) and a column chunk is per-group, so any read of it
7
+ // materialises the whole file. The converted branch uses uniform 10,000-row
8
+ // groups.
9
+ import { LOCAL_PATH } from "../config.js";
10
+ import { hfConvertedParquet, localFiles } from "../discovery.js";
11
+ import { localDir } from "../corpus.js";
12
+ import { DIM, R, YEL } from "../ui.js";
13
+ import { join } from "node:path";
14
+ /** The work-list of a corpus read from Hugging Face's auto-converted
15
+ * `refs/convert/parquet` branch. Shared by 2Wiki, SODA and MASSIVE: they
16
+ * differ in their dataset, their budget and their adapter — nothing else. */
17
+ export function convertedParquetUnits(opts) {
18
+ return async (ctx) => {
19
+ if (LOCAL_PATH) {
20
+ const dir = localDir(opts.localSub);
21
+ const names = localFiles(dir, /\.parquet$/i);
22
+ if (names.length === 0) {
23
+ ctx.progress.log(` ${DIM}· no ${opts.label} *.parquet in ${dir} — skipping${R}`);
24
+ return null;
25
+ }
26
+ return names.map((n) => ({
27
+ key: n.path,
28
+ name: n.path,
29
+ display: `${opts.label} ${n.path}`,
30
+ local: join(dir, n.path),
31
+ bytes: n.size,
32
+ }));
33
+ }
34
+ const paths = await hfConvertedParquet(opts.dataset, opts.config, opts.splits, opts.label, ctx.http, (m) => ctx.progress.log(` ${YEL}⚠${R} ${m}`));
35
+ return paths.map(({ path, size }) => ({
36
+ key: path,
37
+ name: path,
38
+ display: `${opts.label} ${path}`,
39
+ url: `https://huggingface.co/datasets/${opts.dataset}` +
40
+ `/resolve/refs%2Fconvert%2Fparquet/${path}`,
41
+ bytes: size,
42
+ }));
43
+ };
44
+ }
@@ -0,0 +1,14 @@
1
+ import { type TrainingItem } from "../items.js";
2
+ import { type Corpus } from "../corpus.js";
3
+ /** One normalized General-Knowledge row. */
4
+ export interface GenKnowRow {
5
+ question: string;
6
+ answer: string;
7
+ }
8
+ /** Normalize a raw datasets-server row into a GenKnowRow, or null when it lacks
9
+ * a usable question/answer or a side is implausibly large (corruption). */
10
+ export declare function toGenKnowRow(row: unknown, maxChars?: number): GenKnowRow | null;
11
+ /** Translate ONE General-Knowledge row into SEMA items: exactly one
12
+ * (question → answer) FACT. refineItems drops a degenerate question === answer. */
13
+ export declare function genKnowRowToItems(row: GenKnowRow): TrainingItem[];
14
+ export declare const genknow: Corpus;
@@ -0,0 +1,83 @@
1
+ // train_base/corpora/genknow.ts — MuskumPillerum/General-Knowledge Q&A
2
+ //
3
+ // Knobs, the row adapter, and the stage descriptor for ONE corpus, together
4
+ // with the evidence that fixed each default. A corpus file owns everything
5
+ // source-specific; the contract it fills is ../corpus.ts and the loop that runs
6
+ // it is ../stage.ts.
7
+ import { env } from "../config.js";
8
+ import { refineItems } from "../items.js";
9
+ import { jsonArray } from "../readers.js";
10
+ import { singleUnit } from "../corpus.js";
11
+ // ── MuskumPillerum/General-Knowledge (the fourth training stage, after oasst2) ──
12
+ // A ~37.6k-row general-knowledge Q&A set: each row is a single {Question, Answer}
13
+ // pair. A row is a pure RELATION (question → answer), so it becomes exactly ONE
14
+ // FACT, identical in shape to the Aya stage. It ships as a single JSON array
15
+ // file (output.json); we DOWNLOAD it and stream the array. GENKNOW_URL overrides
16
+ // the source.
17
+ //
18
+ // DISABLED BY DEFAULT ON LICENCE GROUNDS (2026-08-13). The HF repo carries NO
19
+ // licence tag and no licence in its card — an earlier header in this file
20
+ // claimed MIT without support — and its own dataset card states it "contains a
21
+ // subset of the alpaca dataset". Alpaca is CC BY-NC 4.0: NonCommercial, which
22
+ // conflicts with Sema's commercial licence. Because a Sema store retains its
23
+ // training text VERBATIM, an unlicensed corpus inside it makes the whole
24
+ // artifact undistributable. See DATASETS.md §3.2. GENKNOW=1 re-enables the
25
+ // stage for local, non-distributed experiments only.
26
+ const GENKNOW = env("GENKNOW", "0") !== "0";
27
+ const GENKNOW_URL = env("GENKNOW_URL", "https://huggingface.co/datasets/MuskumPillerum/General-Knowledge/resolve/main/output.json");
28
+ // A Question/Answer longer than this is skipped (answers run to a few hundred
29
+ // chars; this only guards against a corrupt/runaway field).
30
+ const MAX_GENKNOW_CHARS = Math.max(4_000, Math.floor(Number(env("MAX_GENKNOW_KB", "64")) * 1000) || 64_000);
31
+ /** Turn a source value into clean prose: decode the literal "\n"/"\t"/"\r"
32
+ * two-character escapes the source JSON left in the text, collapse the runs of
33
+ * whitespace that creates, and trim. */
34
+ function unescapePlain(s) {
35
+ return s
36
+ .replace(/\\r\\n|\\n|\\r/g, "\n")
37
+ .replace(/\\t/g, " ")
38
+ .replace(/[ \t]+/g, " ")
39
+ .replace(/\n{3,}/g, "\n\n")
40
+ .trim();
41
+ }
42
+ /** Normalize a raw datasets-server row into a GenKnowRow, or null when it lacks
43
+ * a usable question/answer or a side is implausibly large (corruption). */
44
+ export function toGenKnowRow(row, maxChars = MAX_GENKNOW_CHARS) {
45
+ if (!row || typeof row !== "object")
46
+ return null;
47
+ const r = row;
48
+ const question = typeof r.Question === "string"
49
+ ? unescapePlain(r.Question)
50
+ : "";
51
+ const answer = typeof r.Answer === "string" ? unescapePlain(r.Answer) : "";
52
+ if (!question || !answer)
53
+ return null;
54
+ if (question.length > maxChars || answer.length > maxChars)
55
+ return null;
56
+ return { question, answer };
57
+ }
58
+ /** Translate ONE General-Knowledge row into SEMA items: exactly one
59
+ * (question → answer) FACT. refineItems drops a degenerate question === answer. */
60
+ export function genKnowRowToItems(row) {
61
+ return refineItems([{ context: row.question, continuation: row.answer }]);
62
+ }
63
+ export const genknow = {
64
+ id: "genknow",
65
+ label: "General-Knowledge",
66
+ kind: "Q&A facts",
67
+ enabled: GENKNOW,
68
+ read: jsonArray(),
69
+ toItems: (row) => {
70
+ const r = toGenKnowRow(row);
71
+ return r ? genKnowRowToItems(r) : null;
72
+ },
73
+ discover: singleUnit({
74
+ // Resume id "genknow::qa" — the string this store already records.
75
+ key: "qa",
76
+ label: "General-Knowledge",
77
+ display: "General-Knowledge",
78
+ url: GENKNOW_URL,
79
+ dest: "general_knowledge.json",
80
+ localMatch: [/general.*knowledge.*\.json$/i, /output\.json$/i],
81
+ localWhat: "General-Knowledge *.json",
82
+ }),
83
+ };
@@ -0,0 +1,29 @@
1
+ import type { Corpus } from "../corpus.js";
2
+ export declare const CURRICULUM: Corpus[];
3
+ /** The corpora this RUN will train, for the panel header — derived, so it can
4
+ * never go stale the way the hand-written string it replaces had. */
5
+ export declare const enabledLabels: () => string;
6
+ /** The corpora a STORE contains: everything this run will train PLUS everything
7
+ * an earlier run already did, in curriculum order.
8
+ *
9
+ * Not the same question as `enabledLabels`, and conflating them wrote a false
10
+ * statement into every store that was ever resumed with a different set of
11
+ * stages enabled. Observed on a real store: `train.dataset` read
12
+ * "SmolSent+Aya+oasst2" while the tally recorded 37,623 General-Knowledge
13
+ * deposits sitting in it. That is not cosmetic — a Sema store retains its
14
+ * training text VERBATIM, so `train.dataset` is the record of whose licence
15
+ * terms travel with the artifact, and General-Knowledge is precisely the
16
+ * corpus DATASETS.md §3.2 disables on NonCommercial grounds.
17
+ *
18
+ * `trainedIds` are the corpus ids an earlier run deposited under (the keys of
19
+ * the per-corpus tally). An id no longer in the curriculum cannot be named and
20
+ * is dropped — the tally still carries it, which is where that evidence lives. */
21
+ export declare const storedLabels: (trainedIds: Iterable<string>) => string;
22
+ export * from "./smolsent.js";
23
+ export * from "./aya.js";
24
+ export * from "./oasst2.js";
25
+ export * from "./taskmaster.js";
26
+ export * from "./wiki2.js";
27
+ export * from "./soda.js";
28
+ export * from "./massive.js";
29
+ export * from "./genknow.js";
@@ -0,0 +1,81 @@
1
+ // train_base/corpora/index.ts — THE CURRICULUM.
2
+ //
3
+ // The order is the curriculum, and it is load-bearing: each stage runs only
4
+ // after the previous one finishes, and every stage records itself in the same
5
+ // completed-set, so a single store resumes the whole sequence.
6
+ //
7
+ // 1. SmolSent sentence-level TRANSLATION pairs across 100+ low-resource
8
+ // languages. Each pair is "two names for one meaning" → a
9
+ // foreign→English translation FACT, so every language's
10
+ // rendering of a meaning converges on ONE English node (cf.
11
+ // test/05-concepts.test.mjs).
12
+ // 2. Aya ~204k human prompt→completion pairs, 70+ languages → one
13
+ // (question → answer) FACT each.
14
+ // 3. oasst2 MULTI-TURN human↔assistant conversation trees → the
15
+ // accumulated-context walk (single-turn trees are skipped).
16
+ // 4. Taskmaster task-oriented DIALOGUE, the best-scoring corpora on the
17
+ // fold-unit recurrence benchmark that predicts halo health.
18
+ // 5. 2Wiki the `evidences` TRIPLES — the one stage aimed at
19
+ // COMPOSITION. Its Wikipedia passages and its composed
20
+ // questions are deliberately NOT read.
21
+ // 6. SODA social/commonsense DIALOGUE, budgeted.
22
+ // 7. MASSIVE short intent utterances → ONE bare experience each.
23
+ // DISABLED BY DEFAULT — edge-less content was measured to
24
+ // manufacture answers where the store should stay silent.
25
+ // 8. GenKnow ~37.6k {Question, Answer} pairs → one FACT each. DISABLED
26
+ // BY DEFAULT on licence grounds; see DATASETS.md §3.2.
27
+ import { smolsent } from "./smolsent.js";
28
+ import { aya } from "./aya.js";
29
+ import { oasst2 } from "./oasst2.js";
30
+ import { taskmaster } from "./taskmaster.js";
31
+ import { wiki2 } from "./wiki2.js";
32
+ import { soda } from "./soda.js";
33
+ import { massive } from "./massive.js";
34
+ import { genknow } from "./genknow.js";
35
+ export const CURRICULUM = [
36
+ smolsent,
37
+ aya,
38
+ oasst2,
39
+ taskmaster,
40
+ wiki2,
41
+ soda,
42
+ massive,
43
+ genknow,
44
+ ];
45
+ /** The corpora this RUN will train, for the panel header — derived, so it can
46
+ * never go stale the way the hand-written string it replaces had. */
47
+ export const enabledLabels = () => CURRICULUM.filter((c) => c.enabled).map((c) => c.label).join("+");
48
+ /** The corpora a STORE contains: everything this run will train PLUS everything
49
+ * an earlier run already did, in curriculum order.
50
+ *
51
+ * Not the same question as `enabledLabels`, and conflating them wrote a false
52
+ * statement into every store that was ever resumed with a different set of
53
+ * stages enabled. Observed on a real store: `train.dataset` read
54
+ * "SmolSent+Aya+oasst2" while the tally recorded 37,623 General-Knowledge
55
+ * deposits sitting in it. That is not cosmetic — a Sema store retains its
56
+ * training text VERBATIM, so `train.dataset` is the record of whose licence
57
+ * terms travel with the artifact, and General-Knowledge is precisely the
58
+ * corpus DATASETS.md §3.2 disables on NonCommercial grounds.
59
+ *
60
+ * `trainedIds` are the corpus ids an earlier run deposited under (the keys of
61
+ * the per-corpus tally). An id no longer in the curriculum cannot be named and
62
+ * is dropped — the tally still carries it, which is where that evidence lives. */
63
+ export const storedLabels = (trainedIds) => {
64
+ const trained = new Set(trainedIds);
65
+ return CURRICULUM
66
+ .filter((c) => c.enabled || trained.has(c.id))
67
+ .map((c) => c.label)
68
+ .join("+");
69
+ };
70
+ // One re-export per corpus, carrying the descriptor AND its adapters. The
71
+ // explicit `export { aya, genknow, … }` list that used to sit here named the
72
+ // eight descriptors a second time; the star exports below already provide them,
73
+ // and a hand-kept list of everything is exactly the thing that goes stale.
74
+ export * from "./smolsent.js";
75
+ export * from "./aya.js";
76
+ export * from "./oasst2.js";
77
+ export * from "./taskmaster.js";
78
+ export * from "./wiki2.js";
79
+ export * from "./soda.js";
80
+ export * from "./massive.js";
81
+ export * from "./genknow.js";
@@ -0,0 +1,7 @@
1
+ import { type TrainingItem } from "../items.js";
2
+ import type { Corpus } from "../corpus.js";
3
+ /** Translate ONE MASSIVE row into SEMA items: its bare utterance, as an
4
+ * experience. `annot_utt` (slot-annotated) is deliberately not used — its
5
+ * "[date : ...]" markup is not prose. Returns [] for an unusable row. */
6
+ export declare function massiveRowToItems(row: unknown, maxChars?: number): TrainingItem[];
7
+ export declare const massive: Corpus;
@@ -0,0 +1,98 @@
1
+ // train_base/corpora/massive.ts — AmazonScience/massive short intents
2
+ //
3
+ // Knobs, the row adapter, and the stage descriptor for ONE corpus, together
4
+ // with the evidence that fixed each default. A corpus file owns everything
5
+ // source-specific; the contract it fills is ../corpus.ts and the loop that runs
6
+ // it is ../stage.ts.
7
+ import { env } from "../config.js";
8
+ import { refineItems } from "../items.js";
9
+ import { parquet } from "../readers.js";
10
+ import { convertedParquetUnits } from "./converted-parquet.js";
11
+ // MASSIVE deposits BARE UTTERANCES — an experience, not an episode — and that
12
+ // is the only shape its data supports. Two richer shapes were considered and
13
+ // rejected on evidence:
14
+ // • Same-intent pairs as paraphrases. 49.1% of consecutive rows share
15
+ // (locale, intent), but they are NOT meaning-equivalent: intent 48 in mn-MN
16
+ // runs "wake me at nine on the fifth" next to "set an alarm two hours from
17
+ // now". Depositing that pair as an episode teaches a continuation that does
18
+ // not exist.
19
+ // • Same-id rows across locales. Those ARE translations of one another —
20
+ // which is exactly SmolSent's relation, and SmolSent scores worst of every
21
+ // corpus measured on fold-unit recurrence (23.2%) because cross-lingual
22
+ // pairs share no units.
23
+ // So the stage contributes recurring fold units and lexical coverage (65.1%
24
+ // recurring unit mass, median 29 B) and nothing relational. `annot_utt` carries
25
+ // slot markup ("[date : tavdahad] ...") and is never read.
26
+ // DISABLED BY DEFAULT, on evidence gathered after the stage was written. A bare
27
+ // experience deposits content with NO EDGE, and that cuts both ways. Measured on
28
+ // a three-pair dialogue store with and without six MASSIVE-style utterances:
29
+ //
30
+ // "set an alarm" without: "Sure, what size would you like?" (wrong)
31
+ // with: "set an alarm for seven" (better)
32
+ // "play music" without: "" (correct silence)
33
+ // with: "Yes, sweetened or unsweetened?" (wrong)
34
+ //
35
+ // So it displaces some wrong answers and manufactures others, INCLUDING turning
36
+ // a correct silence into a wrong answer — and honest silence is a stated
37
+ // property of this engine (AGENTS §2.13). On the mixed-curriculum store the
38
+ // same shape produced the fragment "nus" for "wake me up at nine am".
39
+ //
40
+ // That evidence is four probes on toy stores and is NOT conclusive; it is,
41
+ // however, the only evidence there is, and it points the wrong way. The stage
42
+ // stays implemented and one env var away. Turn it on (MASSIVE=1) once there is
43
+ // a real measurement showing the recurring fold units it contributes (72.3% of
44
+ // deposited unit mass) buy more than the spurious answers cost.
45
+ const MASSIVE = env("MASSIVE", "0") !== "0";
46
+ const MASSIVE_DATASET = env("MASSIVE_DATASET", "AmazonScience/massive");
47
+ // "all" is the config covering every locale in one set of shards.
48
+ const MASSIVE_CONFIG = env("MASSIVE_CONFIG", "all");
49
+ const MASSIVE_SPLITS = env("MASSIVE_SPLITS", "train")
50
+ .split(",").map((s) => s.trim()).filter(Boolean);
51
+ // 0 = every row (587,214 in `all`/train, ~17 MB of content).
52
+ const MASSIVE_MAX_ROWS = Math.max(0, Math.floor(Number(env("MASSIVE_MAX_ROWS", "0"))) || 0);
53
+ const MAX_MASSIVE_UTT_CHARS = Math.max(100, Math.floor(Number(env("MAX_MASSIVE_UTT_KB", "2")) * 1000) || 2_000);
54
+ // ═══════════════════════════════════════════════════════════════════════
55
+ // §6e⁗ MASSIVE parsing — one short utterance → ONE SEMA experience
56
+ //
57
+ // See the constants note for why this deposits a bare experience and not a
58
+ // relation: the two relational shapes this corpus appears to offer are both
59
+ // false (same-intent rows are not paraphrases; same-id rows across locales are
60
+ // translations, SmolSent's worst-scoring relation).
61
+ // ═══════════════════════════════════════════════════════════════════════
62
+ /** Translate ONE MASSIVE row into SEMA items: its bare utterance, as an
63
+ * experience. `annot_utt` (slot-annotated) is deliberately not used — its
64
+ * "[date : ...]" markup is not prose. Returns [] for an unusable row. */
65
+ export function massiveRowToItems(row, maxChars = MAX_MASSIVE_UTT_CHARS) {
66
+ if (!row || typeof row !== "object")
67
+ return [];
68
+ const utt = row.utt;
69
+ const text = typeof utt === "string" ? utt.trim() : "";
70
+ if (!text || text.length > maxChars)
71
+ return [];
72
+ return refineItems([text]);
73
+ }
74
+ export const massive = {
75
+ id: "massive",
76
+ label: "MASSIVE",
77
+ kind: "short intents",
78
+ enabled: MASSIVE,
79
+ maxRows: MASSIVE_MAX_ROWS,
80
+ // One of ten columns — `annot_utt`, whose slot markup the note above rejects,
81
+ // is not decoded either. Measured on the converted train shard: 92 MB
82
+ // uncompressed across all ten, 36 MB for `utt` (38.7%).
83
+ read: parquet({ columns: ["utt"] }),
84
+ toItems: (row) => {
85
+ const items = massiveRowToItems(row);
86
+ return items.length ? items : null;
87
+ },
88
+ unitNoun: "shard(s)",
89
+ log: { rows: "row(s)" },
90
+ discover: convertedParquetUnits({
91
+ id: "massive",
92
+ label: "MASSIVE",
93
+ dataset: MASSIVE_DATASET,
94
+ config: MASSIVE_CONFIG,
95
+ splits: MASSIVE_SPLITS,
96
+ localSub: "massive",
97
+ }),
98
+ };
@@ -0,0 +1,52 @@
1
+ import { type TrainingItem } from "../items.js";
2
+ import { type Corpus } from "../corpus.js";
3
+ /** A single oasst2 message node (the fields we use; the tree nests via replies). */
4
+ interface OasstNode {
5
+ role?: string;
6
+ text?: string;
7
+ rank?: number | null;
8
+ deleted?: boolean;
9
+ replies?: OasstNode[];
10
+ }
11
+ /** One conversational turn extracted from a tree. */
12
+ export interface OasstTurn {
13
+ role: string;
14
+ text: string;
15
+ }
16
+ /** Collapse a conversation tree to ONE linear path: at each node, descend into
17
+ * its best-ranked, non-deleted reply (rank 0 preferred; unranked sorts last).
18
+ * Returns the ordered turns (already strictly alternating in this corpus). */
19
+ export declare function bestOasstPath(root: OasstNode): OasstTurn[];
20
+ /** Translate ONE multi-turn oasst2 conversation into SEMA training items.
21
+ *
22
+ * This is the ONE stage where cumulative continuous context is truly necessary:
23
+ * the data is a real multi-turn dialogue, and what must be learned is how each
24
+ * turn follows from the WHOLE conversation so far — not from the previous turn
25
+ * alone. The conversation is emitted ONLY as the accumulated walk; standalone
26
+ * turn experiences and local adjacent-pair facts are NOT emitted (they are
27
+ * subsumed by it and would merely replicate the content).
28
+ *
29
+ * The walk is the pattern proven in test/13-conversation.test.mjs
30
+ * ("teachConversation"): each turn is the continuation of all prior turns,
31
+ * with BARE turn text — NO "User:/Assistant:" labels. The SHAPE is identical
32
+ * (cumulative context → next turn); the join string is not, and does not need
33
+ * to be — that file joins with nothing and this corpus joins with "\n" (see
34
+ * `accumulate`). Saying "byte-for-byte", as this comment used to, invites the
35
+ * reading that the two must agree on a separator. They must not agree,
36
+ * because there is nothing to agree about: turn boundaries are offsets, and
37
+ * the join string is just corpus text. Roles already
38
+ * alternate by position in an oasst2 best-path (the root is a prompter), so a
39
+ * label adds nothing the position does not, while a clean continuation matches
40
+ * the test's recall (predictNext queries bare prior turns) and lets a turn share
41
+ * its gist with the same text elsewhere (e.g. an Aya question stored bare).
42
+ *
43
+ * Returns [] for a conversation below the multi-turn threshold, so callers can
44
+ * simply skip empties. */
45
+ export declare function oasstConversationToItems(turns: OasstTurn[], minTurns?: number): TrainingItem[];
46
+ /** The row adapter: ONE line of the tree dump → its deposits. Returns null for
47
+ * a tree with no prompt and for every single-turn tree — the latter is the
48
+ * stage's design, not a defect, which is why the reader counts it `unusable`
49
+ * rather than `skipped`. */
50
+ export declare function oasstTreeToItems(row: unknown): TrainingItem[] | null;
51
+ export declare const oasst2: Corpus;
52
+ export {};
@@ -0,0 +1,120 @@
1
+ // train_base/corpora/oasst2.ts — OpenAssistant/oasst2 conversation trees
2
+ //
3
+ // Knobs, the row adapter, and the stage descriptor for ONE corpus, together
4
+ // with the evidence that fixed each default. A corpus file owns everything
5
+ // source-specific; the contract it fills is ../corpus.ts and the loop that runs
6
+ // it is ../stage.ts.
7
+ import { env } from "../config.js";
8
+ import { accumulate, refineItems } from "../items.js";
9
+ import { lines } from "../readers.js";
10
+ import { singleUnit } from "../corpus.js";
11
+ // ── OpenAssistant/oasst2 (the fourth training stage, after Aya) ──
12
+ // oasst2 is a corpus of human↔assistant conversation TREES. Its richest, most
13
+ // stream-friendly artifact is "<date>_oasst2_ready.trees.jsonl.gz": one JSON
14
+ // conversation tree PER LINE, gzip-compressed (a web standard — Decompression
15
+ // Stream("gzip")). Each tree is {message_tree_id, prompt:{role,text,replies:[…]}}
16
+ // where `replies` nests recursively and a prompt can have several ranked
17
+ // assistant replies (rank 0 = best). We follow the best-ranked, non-deleted
18
+ // reply at each step to get ONE linear, strictly-alternating conversation per
19
+ // tree, then keep only the MULTI-TURN ones (≥ OASST_MIN_TURNS messages, i.e. at
20
+ // least two full user→assistant exchanges) — single Q→A trees are skipped, by
21
+ // design. OASST=0 disables the stage; OASST_URL overrides the source.
22
+ const OASST = env("OASST", "1") !== "0";
23
+ const OASST_URL = env("OASST_URL", "https://huggingface.co/datasets/OpenAssistant/oasst2/resolve/main/2023-11-05_oasst2_ready.trees.jsonl.gz");
24
+ // Multi-turn threshold: a conversation must have at least this many turns to be
25
+ // trained (4 = user→assistant→user→assistant, the smallest real multi-turn).
26
+ const OASST_MIN_TURNS = Math.max(2, Math.floor(Number(env("OASST_MIN_TURNS", "4"))) || 4);
27
+ // Skip a tree whose decoded JSON line exceeds this (a pathological record); the
28
+ // real maximum is far smaller, so this only guards against corruption.
29
+ const MAX_OASST_LINE_CHARS = Math.max(100_000, Math.floor(Number(env("MAX_OASST_LINE_MB", "8")) * 1_000_000) || 8_000_000);
30
+ /** Collapse a conversation tree to ONE linear path: at each node, descend into
31
+ * its best-ranked, non-deleted reply (rank 0 preferred; unranked sorts last).
32
+ * Returns the ordered turns (already strictly alternating in this corpus). */
33
+ export function bestOasstPath(root) {
34
+ const turns = [];
35
+ let node = root;
36
+ while (node) {
37
+ const text = typeof node.text === "string" ? node.text.trim() : "";
38
+ if (text)
39
+ turns.push({ role: String(node.role ?? "?"), text });
40
+ const live = (node.replies ?? []).filter((r) => r && !r.deleted && typeof r.text === "string" && r.text.trim() !== "");
41
+ if (live.length === 0)
42
+ break;
43
+ live.sort((a, b) => (a.rank ?? Number.MAX_SAFE_INTEGER) - (b.rank ?? Number.MAX_SAFE_INTEGER));
44
+ node = live[0];
45
+ }
46
+ return turns;
47
+ }
48
+ /** Translate ONE multi-turn oasst2 conversation into SEMA training items.
49
+ *
50
+ * This is the ONE stage where cumulative continuous context is truly necessary:
51
+ * the data is a real multi-turn dialogue, and what must be learned is how each
52
+ * turn follows from the WHOLE conversation so far — not from the previous turn
53
+ * alone. The conversation is emitted ONLY as the accumulated walk; standalone
54
+ * turn experiences and local adjacent-pair facts are NOT emitted (they are
55
+ * subsumed by it and would merely replicate the content).
56
+ *
57
+ * The walk is the pattern proven in test/13-conversation.test.mjs
58
+ * ("teachConversation"): each turn is the continuation of all prior turns,
59
+ * with BARE turn text — NO "User:/Assistant:" labels. The SHAPE is identical
60
+ * (cumulative context → next turn); the join string is not, and does not need
61
+ * to be — that file joins with nothing and this corpus joins with "\n" (see
62
+ * `accumulate`). Saying "byte-for-byte", as this comment used to, invites the
63
+ * reading that the two must agree on a separator. They must not agree,
64
+ * because there is nothing to agree about: turn boundaries are offsets, and
65
+ * the join string is just corpus text. Roles already
66
+ * alternate by position in an oasst2 best-path (the root is a prompter), so a
67
+ * label adds nothing the position does not, while a clean continuation matches
68
+ * the test's recall (predictNext queries bare prior turns) and lets a turn share
69
+ * its gist with the same text elsewhere (e.g. an Aya question stored bare).
70
+ *
71
+ * Returns [] for a conversation below the multi-turn threshold, so callers can
72
+ * simply skip empties. */
73
+ export function oasstConversationToItems(turns, minTurns = OASST_MIN_TURNS) {
74
+ if (turns.length < minTurns)
75
+ return []; // not multi-turn — skip
76
+ return refineItems(accumulate(turns.map((t) => t.text)));
77
+ }
78
+ /** The row adapter: ONE line of the tree dump → its deposits. Returns null for
79
+ * a tree with no prompt and for every single-turn tree — the latter is the
80
+ * stage's design, not a defect, which is why the reader counts it `unusable`
81
+ * rather than `skipped`. */
82
+ export function oasstTreeToItems(row) {
83
+ if (!row || typeof row !== "object")
84
+ return null;
85
+ const tree = row;
86
+ if (!tree.prompt)
87
+ return null;
88
+ const items = oasstConversationToItems(bestOasstPath(tree.prompt));
89
+ return items.length ? items : null;
90
+ }
91
+ export const oasst2 = {
92
+ id: "oasst2",
93
+ label: "oasst2",
94
+ kind: "multi-turn chat",
95
+ enabled: OASST,
96
+ read: lines({ gzip: true, maxLineChars: MAX_OASST_LINE_CHARS }),
97
+ toItems: oasstTreeToItems,
98
+ // The one corpus that KEEPS a cached file after a complete read: a copy left
99
+ // behind by a previous interrupted run is not this stage's to reclaim.
100
+ keepCached: true,
101
+ log: {
102
+ deposits: "examples",
103
+ // A tree that yields items is exactly a tree that cleared OASST_MIN_TURNS.
104
+ rows: "conversation(s)",
105
+ // The single-turn trees this stage drops BY DESIGN must not be reported as
106
+ // damage — only genuinely malformed lines are.
107
+ malformedOnly: true,
108
+ },
109
+ discover: singleUnit({
110
+ // Resume id "oasst2::trees" — the string this store already records.
111
+ key: "trees",
112
+ label: "oasst2",
113
+ display: "oasst2 (multi-turn)",
114
+ url: OASST_URL,
115
+ dest: "oasst2_ready.trees.jsonl.gz",
116
+ acquireLabel: "oasst2 trees",
117
+ localMatch: [/oasst.*trees.*\.jsonl\.gz$/i, /oasst.*\.jsonl\.gz$/i],
118
+ localWhat: "oasst2 *trees*.jsonl.gz",
119
+ }),
120
+ };
@@ -0,0 +1,23 @@
1
+ import { type TrainingItem } from "../items.js";
2
+ import type { Corpus } from "../corpus.js";
3
+ /** One normalized SmolSent row. */
4
+ export interface SmolSentRow {
5
+ src: string;
6
+ trg: string;
7
+ sl: string;
8
+ tl: string;
9
+ }
10
+ /** Normalize a raw datasets-server row into a SmolSentRow, or null when it lacks
11
+ * both sides or a side is implausibly large (a dump, not a sentence). */
12
+ export declare function toSmolSentRow(row: unknown, maxChars?: number): SmolSentRow | null;
13
+ /** Translate ONE SmolSent pair into SEMA facts. The two sentences are one
14
+ * meaning in two languages, but the two BINDINGS are not equally sound —
15
+ * SmolSent's English side is a shared pool translated into every language, so
16
+ * `trg -> src` gives one English context a different answer in every language
17
+ * file. See SMOLSENT_DIRECTIONS. refineItems drops the degenerate case where
18
+ * src === trg. */
19
+ export declare function smolSentRowToItems(row: SmolSentRow, dirs?: {
20
+ src2trg: boolean;
21
+ trg2src: boolean;
22
+ }): TrainingItem[];
23
+ export declare const smolsent: Corpus;