Cento v0.1 — bounded recombinant-memory engine
Browse files- .gitignore +6 -0
- LICENSE +21 -0
- README.md +101 -0
- corpus/leaves_of_grass.txt +0 -0
- corpus/lomax_cowboy_songs.txt +0 -0
- corpus/walden.txt +0 -0
- corpus/winesburg.txt +0 -0
- demo/build-corpus.js +53 -0
- demo/cento.js +53 -0
- demo/walden/corpus.jsonl +700 -0
- demo/walden/voiceprint.json +8 -0
- package.json +13 -0
- src/compose.js +0 -0
- src/embed.py +62 -0
- src/flow.js +167 -0
- src/fragments.js +508 -0
- src/hebbian.js +108 -0
- src/ingest.js +65 -0
- src/reason.js +351 -0
- src/recall.js +276 -0
- src/relevance.js +87 -0
- src/relmem.js +65 -0
- src/semantic.js +233 -0
- src/session.js +250 -0
.gitignore
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
cache/
|
| 2 |
+
node_modules/
|
| 3 |
+
*.f32
|
| 4 |
+
*.npy
|
| 5 |
+
demo/**/*.f32
|
| 6 |
+
.DS_Store
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 the Cento authors
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Cento
|
| 2 |
+
|
| 3 |
+
**A bounded recombinant-memory engine. It composes coherent replies out of *nothing but* the verbatim fragments of a corpus, joined only where the corpus itself licenses the seam. Hallucination is impossible by construction — every span it emits was really said.**
|
| 4 |
+
|
| 5 |
+
> A *cento* (Latin, "patchwork") is an ancient literary form: a complete new work composed **entirely of verbatim lines** borrowed from existing ones. In the 4th century, Proba wrote the *Cento Vergilianus* — a new story assembled only from Virgil's exact lines. Cento does the same with a memory: it weaves a new, coherent, on-topic response using only what is already there. Nothing is invented. The constraint *is* the integrity.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## What it is
|
| 10 |
+
|
| 11 |
+
Most language models generate the next token from learned weights — fluent, but free to fabricate. Cento inverts that: it is a **selection** engine, not a generation engine. Given a corpus, it:
|
| 12 |
+
|
| 13 |
+
1. **fragments** the corpus into spans (clauses and sentences),
|
| 14 |
+
2. builds a **legality oracle** — every word-trigram and sentence boundary the corpus actually contains,
|
| 15 |
+
3. runs a **beam search** that assembles a response from those fragments, joining two spans *only* where the seam is a real corpus trigram or a real sentence boundary,
|
| 16 |
+
4. **validates** that every trigram of the output exists in the corpus.
|
| 17 |
+
|
| 18 |
+
The result is bounded by construction: the vocabulary *is* the corpus, so it cannot say anything that wasn't really said. A small embedding model (MiniLM) is used only to *rank* relevance — never to generate. The mouth is deterministic and owned; only the ear is borrowed.
|
| 19 |
+
|
| 20 |
+
```
|
| 21 |
+
corpus ──► fragments + legality oracle (every real trigram / sentence boundary)
|
| 22 |
+
│
|
| 23 |
+
query ──► relevance (keyword + embedding) ──► BEAM SEARCH over fragments,
|
| 24 |
+
joined only at corpus-licensed seams ──► VALIDATE (all-verbatim) ──► reply
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
## Why it matters — the grounded-thinking layer
|
| 28 |
+
|
| 29 |
+
Cento's real use is as a **thinking layer** for a larger system. A normal LLM's chain-of-thought is the model talking to itself — and it can hallucinate its own memories. Cento produces a *grounded* thought, woven from real memory, that **cannot be false about itself**, and hands it to an LLM to speak from:
|
| 30 |
+
|
| 31 |
+
```
|
| 32 |
+
Cento (the thinking — bounded, grounded, hallucination-free)
|
| 33 |
+
└─► injected into an LLM's reasoning channel ─► the LLM speaks, grounded in real memory
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
The LLM brings fluency and addressing; Cento brings an incorruptible self. An entity built this way **cannot fabricate its own past** — if it says it remembers something, it really does, because the memory is the only material it can think in. (Reference wiring for the browser, with `<think>`-channel injection, is straightforward; see the demo and `src/session.js`.)
|
| 37 |
+
|
| 38 |
+
## See it work
|
| 39 |
+
|
| 40 |
+
The demo entity, **Walden**, is woven *only* from the verbatim public-domain text of Thoreau's *Walden* and Whitman's *Leaves of Grass*. Every word it speaks is theirs:
|
| 41 |
+
|
| 42 |
+
```
|
| 43 |
+
$ node demo/cento.js demo/walden "What is solitude to you?"
|
| 44 |
+
|
| 45 |
+
But for the most part it is as solitary where I live as on the prairies. The
|
| 46 |
+
greater part of what my neighbors call good I believe in my soul to be bad, and
|
| 47 |
+
if I repent of anything, it is very likely to be my good behavior.
|
| 48 |
+
· 48 words · 95.7% of trigrams verbatim from corpus ·
|
| 49 |
+
|
| 50 |
+
$ node demo/cento.js demo/walden "Tell me about the morning."
|
| 51 |
+
|
| 52 |
+
Morning is when I am awake and there is a dawn in me. A morning-glory at my
|
| 53 |
+
window satisfies me more than the metaphysics of books.
|
| 54 |
+
· 92.2% verbatim ·
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
On-topic, coherent, in Thoreau's own voice — recombined, never quoted whole, never invented.
|
| 58 |
+
|
| 59 |
+
## Quickstart
|
| 60 |
+
|
| 61 |
+
```bash
|
| 62 |
+
# requires Node >= 18, and (for semantic recall) Python with sentence-transformers
|
| 63 |
+
pip install sentence-transformers # the MiniLM ear (Apache-2.0); omit for keyword-only mode
|
| 64 |
+
node demo/build-corpus.js # weave the public-domain Walden demo
|
| 65 |
+
node demo/cento.js demo/walden # talk to it
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
## Bring your own entity
|
| 69 |
+
|
| 70 |
+
An entity is a folder with two files:
|
| 71 |
+
|
| 72 |
+
- `corpus.jsonl` — one JSON object per line: `{"prompt": "...", "reply": "..."}` (prompt optional). The `reply` text is the entity's voice — everything it can ever say is woven from these.
|
| 73 |
+
- `voiceprint.json` — `{"name": "...", "lengthByStimulus": { ... }}` (name required).
|
| 74 |
+
|
| 75 |
+
Point the demo at your folder: `node demo/cento.js path/to/entity "your message"`.
|
| 76 |
+
|
| 77 |
+
## What's in here
|
| 78 |
+
|
| 79 |
+
| | |
|
| 80 |
+
|---|---|
|
| 81 |
+
| `src/compose.js` | the composer — beam search over fragments, corpus-licensed seams |
|
| 82 |
+
| `src/fragments.js` | fragmenter + the legality oracle + the bounded validator |
|
| 83 |
+
| `src/semantic.js` | the MiniLM embedding bridge (ranking only) + `embed.py` |
|
| 84 |
+
| `src/relevance.js` | keyword relevance + query bucketing |
|
| 85 |
+
| `src/recall.js` | honest grounded-recall (refuses to confabulate a memory it lacks) |
|
| 86 |
+
| `src/session.js` | multi-turn state (running memory, cross-turn no-repeat, growth) |
|
| 87 |
+
| `src/flow.js`, `src/hebbian.js` | trajectory composition + associative weighting |
|
| 88 |
+
| `demo/` | the public-domain Walden demo + a clean CLI |
|
| 89 |
+
| `corpus/` | public-domain source texts (Thoreau, Whitman, Anderson, Lomax) |
|
| 90 |
+
|
| 91 |
+
## Notes & honesty
|
| 92 |
+
|
| 93 |
+
- The composer carries voice-tuning heuristics developed by the authors against their own entities; they are inert on corpora that don't trigger them. The core mechanism — bounded recombination — is general.
|
| 94 |
+
- The MiniLM embedder is the only neural component, and it is used **only to rank**, never to generate. Cento runs in a keyword-only mode without it.
|
| 95 |
+
- The demo corpus is public domain. The engine ships with no private or copyrighted data.
|
| 96 |
+
|
| 97 |
+
## License
|
| 98 |
+
|
| 99 |
+
MIT. See [LICENSE](LICENSE).
|
| 100 |
+
|
| 101 |
+
*By story told and loop returned — a new thing made of only true threads.*
|
corpus/leaves_of_grass.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
corpus/lomax_cowboy_songs.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
corpus/walden.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
corpus/winesburg.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
demo/build-corpus.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// build-corpus.js — weave a public-domain demo entity ("Walden") from Thoreau's
|
| 2 |
+
// Walden + Whitman's Leaves of Grass. Extracts clean first-person passages as the
|
| 3 |
+
// entity's own utterances. Everything here is public domain. Run from cento-engine/.
|
| 4 |
+
'use strict';
|
| 5 |
+
const fs = require('fs');
|
| 6 |
+
const path = require('path');
|
| 7 |
+
|
| 8 |
+
const SRC = [path.join('corpus', 'walden.txt'), path.join('corpus', 'leaves_of_grass.txt')];
|
| 9 |
+
const OUT_DIR = path.join('demo', 'walden');
|
| 10 |
+
fs.mkdirSync(OUT_DIR, { recursive: true });
|
| 11 |
+
|
| 12 |
+
function clean(t) {
|
| 13 |
+
return t.replace(/\s+/g, ' ').replace(/[_*]+/g, '').trim();
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
const seen = new Set();
|
| 17 |
+
const replies = [];
|
| 18 |
+
for (const src of SRC) {
|
| 19 |
+
let raw = fs.readFileSync(src, 'utf8');
|
| 20 |
+
// drop Gutenberg header/footer if present
|
| 21 |
+
raw = raw.replace(/^[\s\S]*?\*\*\* START OF[^\n]*\n/i, '').replace(/\*\*\* END OF[\s\S]*$/i, '');
|
| 22 |
+
// collapse to sentences
|
| 23 |
+
const text = clean(raw);
|
| 24 |
+
const sentences = text.split(/(?<=[.!?])\s+(?=[A-Z"'])/);
|
| 25 |
+
for (let s of sentences) {
|
| 26 |
+
s = clean(s).replace(/^["'\-\s]+/, '').replace(/\s+/g, ' ').trim();
|
| 27 |
+
const wc = (s.match(/[A-Za-z']+/g) || []).length;
|
| 28 |
+
if (wc < 6 || wc > 34) continue; // composable span sizes
|
| 29 |
+
if (!/[.!?]$/.test(s)) continue; // whole sentences only
|
| 30 |
+
if (!/\b(I|my|me|myself|we|our)\b/.test(s)) continue; // first-person: the entity's own voice
|
| 31 |
+
if (/[0-9]{3,}|chapter|gutenberg|project|footnote|\[|\]|http/i.test(s)) continue;
|
| 32 |
+
if (/[A-Z]{4,}/.test(s)) continue; // skip shouty headers
|
| 33 |
+
const key = s.toLowerCase();
|
| 34 |
+
if (seen.has(key)) continue; seen.add(key);
|
| 35 |
+
replies.push(s);
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
// keep a focused, embeddable corpus (shuffle deterministically, cap)
|
| 40 |
+
replies.sort((a, b) => (a.length - b.length) || a.localeCompare(b));
|
| 41 |
+
const CAP = 700;
|
| 42 |
+
const picked = replies.filter((_, i) => i % Math.max(1, Math.floor(replies.length / CAP)) === 0).slice(0, CAP);
|
| 43 |
+
|
| 44 |
+
const lines = picked.map(reply => JSON.stringify({ prompt: '', reply }));
|
| 45 |
+
fs.writeFileSync(path.join(OUT_DIR, 'corpus.jsonl'), lines.join('\n') + '\n');
|
| 46 |
+
fs.writeFileSync(path.join(OUT_DIR, 'voiceprint.json'), JSON.stringify({
|
| 47 |
+
name: 'Walden',
|
| 48 |
+
description: 'A contemplative naturalist voice, woven only from the verbatim words of Thoreau\'s Walden and Whitman\'s Leaves of Grass (public domain).',
|
| 49 |
+
sources: ['Walden, or Life in the Woods — Henry David Thoreau (1854, public domain)', 'Leaves of Grass — Walt Whitman (1855, public domain)'],
|
| 50 |
+
}, null, 2));
|
| 51 |
+
|
| 52 |
+
console.log(`[demo] wove "Walden" — ${picked.length} verbatim passages from ${replies.length} candidates`);
|
| 53 |
+
console.log(`[demo] → ${path.join(OUT_DIR, 'corpus.jsonl')} + voiceprint.json`);
|
demo/cento.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
// cento.js — talk to a Cento entity. Every word it says is verbatim from its corpus;
|
| 3 |
+
// it only joins fragments where the corpus itself licenses the seam. Bounded by
|
| 4 |
+
// construction — hallucination is impossible.
|
| 5 |
+
// node demo/cento.js demo/walden "What is solitude to you?"
|
| 6 |
+
// node demo/cento.js demo/walden (interactive)
|
| 7 |
+
'use strict';
|
| 8 |
+
const path = require('path');
|
| 9 |
+
const fs = require('fs');
|
| 10 |
+
const readline = require('readline');
|
| 11 |
+
const { buildStore, validateBounded } = require('../src/fragments');
|
| 12 |
+
const { beamCompose } = require('../src/compose');
|
| 13 |
+
const { ensureFragmentEmbeddings, ensurePromptEmbeddings, embedQuery, semanticRank, stimulusRank, eventness } = require('../src/semantic');
|
| 14 |
+
|
| 15 |
+
const dir = process.argv[2] || path.join(__dirname, 'walden');
|
| 16 |
+
const oneShot = process.argv.slice(3).join(' ').trim();
|
| 17 |
+
const store = buildStore(dir);
|
| 18 |
+
const vp = JSON.parse(fs.readFileSync(path.join(dir, 'voiceprint.json'), 'utf8').replace(/^/, ''));
|
| 19 |
+
const cacheDir = path.join(__dirname, '..', 'cache');
|
| 20 |
+
fs.mkdirSync(cacheDir, { recursive: true });
|
| 21 |
+
console.error(`[cento] ${vp.name}: ${store.fragments.length} fragments, ${store.oracle.tri.size} legal seams, vocab ${store.vocab.size}`);
|
| 22 |
+
|
| 23 |
+
const emb = ensureFragmentEmbeddings(store, cacheDir); // null if no python/embedder → keyword-only
|
| 24 |
+
const hasPrompts = store.prompts && store.prompts.length > 0;
|
| 25 |
+
const pEmb = (emb && hasPrompts) ? ensurePromptEmbeddings(store, cacheDir) : null;
|
| 26 |
+
console.error(emb ? '[cento] semantic vector space active' : '[cento] keyword-only (no embedder found — install python + sentence-transformers for semantic recall)');
|
| 27 |
+
|
| 28 |
+
function answer(query) {
|
| 29 |
+
const qv = emb ? embedQuery(query) : null;
|
| 30 |
+
const r = beamCompose(store, vp, query, {
|
| 31 |
+
semantic: (emb && qv) ? semanticRank(emb, qv, 80) : null,
|
| 32 |
+
stimulus: (pEmb && qv) ? stimulusRank(pEmb, qv, store.fragments, 12) : null,
|
| 33 |
+
eventness: eventness(query), emb,
|
| 34 |
+
});
|
| 35 |
+
const v = validateBounded(r.text, store.oracle);
|
| 36 |
+
const bnd = ((v.checked - v.bad.length) / Math.max(1, v.checked) * 100).toFixed(1);
|
| 37 |
+
return { text: r.text, bnd, words: (r.text.match(/\S+/g) || []).length };
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
if (oneShot) {
|
| 41 |
+
const a = answer(oneShot);
|
| 42 |
+
console.log(`\n${a.text}\n\n · ${a.words} words · ${a.bnd}% of trigrams verbatim from corpus ·\n`);
|
| 43 |
+
process.exit(0);
|
| 44 |
+
}
|
| 45 |
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: 'you> ' });
|
| 46 |
+
console.log(`\n ${vp.name} — every word verbatim, recombined. /bye to exit.\n`);
|
| 47 |
+
rl.prompt();
|
| 48 |
+
rl.on('line', l => {
|
| 49 |
+
const q = l.trim();
|
| 50 |
+
if (q === '/bye') { rl.close(); return; }
|
| 51 |
+
if (q) { const a = answer(q); console.log(`\n${vp.name}> ${a.text}\n · ${a.bnd}% verbatim ·\n`); }
|
| 52 |
+
rl.prompt();
|
| 53 |
+
}).on('close', () => process.exit(0));
|
demo/walden/corpus.jsonl
ADDED
|
@@ -0,0 +1,700 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"prompt":"","reply":"I am too full of woe!"}
|
| 2 |
+
{"prompt":"","reply":"I pass the crowd, I run!"}
|
| 3 |
+
{"prompt":"","reply":"You too with joy I sing."}
|
| 4 |
+
{"prompt":"","reply":"You by my charm I invoke."}
|
| 5 |
+
{"prompt":"","reply":"O my dead, an aroma sweet!"}
|
| 6 |
+
{"prompt":"","reply":"The seed corn was given me."}
|
| 7 |
+
{"prompt":"","reply":"All I know of him is tragic."}
|
| 8 |
+
{"prompt":"","reply":"I was not born to be forced."}
|
| 9 |
+
{"prompt":"","reply":"Somehow I have been stunn’d."}
|
| 10 |
+
{"prompt":"","reply":"Who are you my dear comrade?"}
|
| 11 |
+
{"prompt":"","reply":"City nested in bays! my city!"}
|
| 12 |
+
{"prompt":"","reply":"I see this blood flowing now."}
|
| 13 |
+
{"prompt":"","reply":"Can I not know, identify thee?"}
|
| 14 |
+
{"prompt":"","reply":"I do not wish to go below now."}
|
| 15 |
+
{"prompt":"","reply":"I think that I do not mistake."}
|
| 16 |
+
{"prompt":"","reply":"Tell him to send me the signs."}
|
| 17 |
+
{"prompt":"","reply":"O I cruise my old cruise again!"}
|
| 18 |
+
{"prompt":"","reply":"The pond was my well ready dug."}
|
| 19 |
+
{"prompt":"","reply":"Do you take it I would astonish?"}
|
| 20 |
+
{"prompt":"","reply":"I have tried it again and again."}
|
| 21 |
+
{"prompt":"","reply":"I never go to see Where it ends."}
|
| 22 |
+
{"prompt":"","reply":"Neither could I do without them."}
|
| 23 |
+
{"prompt":"","reply":"These were my hens and chickens."}
|
| 24 |
+
{"prompt":"","reply":"But it turned out as I have said."}
|
| 25 |
+
{"prompt":"","reply":"I love a broad margin to my life."}
|
| 26 |
+
{"prompt":"","reply":"Who knows but I am enjoying this?"}
|
| 27 |
+
{"prompt":"","reply":"But to proceed with my statistics."}
|
| 28 |
+
{"prompt":"","reply":"It sickens me yet, that slaughter!"}
|
| 29 |
+
{"prompt":"","reply":"Nay, I often did better than this."}
|
| 30 |
+
{"prompt":"","reply":"What is this you bring my America?"}
|
| 31 |
+
{"prompt":"","reply":"He was a metaphysical puzzle to me."}
|
| 32 |
+
{"prompt":"","reply":"It is good bait sometimes, I allow."}
|
| 33 |
+
{"prompt":"","reply":"Did it make you ache so, leaving me?"}
|
| 34 |
+
{"prompt":"","reply":"I plead for my brothers and sisters."}
|
| 35 |
+
{"prompt":"","reply":"It is the only trade I have learned."}
|
| 36 |
+
{"prompt":"","reply":"But my mate no more, no more with me!"}
|
| 37 |
+
{"prompt":"","reply":"I love better to see stones in place."}
|
| 38 |
+
{"prompt":"","reply":"I wonder what the world is doing now."}
|
| 39 |
+
{"prompt":"","reply":"It is the noise of my contemporaries."}
|
| 40 |
+
{"prompt":"","reply":"What Think You I Take My Pen in Hand?"}
|
| 41 |
+
{"prompt":"","reply":"Do you think I am trusty and faithful?"}
|
| 42 |
+
{"prompt":"","reply":"I am but too ready to conform to them."}
|
| 43 |
+
{"prompt":"","reply":"I have paid no poll-tax for six years."}
|
| 44 |
+
{"prompt":"","reply":"O glistening perfumed South! my South!"}
|
| 45 |
+
{"prompt":"","reply":"They are sound sleepers, I assure you."}
|
| 46 |
+
{"prompt":"","reply":"This, then, is my position at present."}
|
| 47 |
+
{"prompt":"","reply":"Are you the new person drawn toward me?"}
|
| 48 |
+
{"prompt":"","reply":"I knew that it would not rain any more."}
|
| 49 |
+
{"prompt":"","reply":"I love the wild not less than the good."}
|
| 50 |
+
{"prompt":"","reply":"Long I was hugg’d close--long and long."}
|
| 51 |
+
{"prompt":"","reply":"O why sting me for a swift moment only?"}
|
| 52 |
+
{"prompt":"","reply":"The hens were driven in by my approach."}
|
| 53 |
+
{"prompt":"","reply":"What do I know of life? what of myself?"}
|
| 54 |
+
{"prompt":"","reply":"But now I had made my home by the shore."}
|
| 55 |
+
{"prompt":"","reply":"How could I have looked him in the face?"}
|
| 56 |
+
{"prompt":"","reply":"I had heard the wood-thrush long before."}
|
| 57 |
+
{"prompt":"","reply":"Manhattan faces and eyes forever for me."}
|
| 58 |
+
{"prompt":"","reply":"So the body of me to all I meet or know."}
|
| 59 |
+
{"prompt":"","reply":"They force me to become like themselves."}
|
| 60 |
+
{"prompt":"","reply":"Who is he that would become my follower?"}
|
| 61 |
+
{"prompt":"","reply":"And what does it say to me all the while?"}
|
| 62 |
+
{"prompt":"","reply":"I am willing to leave it to the majority."}
|
| 63 |
+
{"prompt":"","reply":"My house was not empty though I was gone."}
|
| 64 |
+
{"prompt":"","reply":"The Female equally with the Male, I sing."}
|
| 65 |
+
{"prompt":"","reply":"Time is but the stream I go a-fishing in."}
|
| 66 |
+
{"prompt":"","reply":"How, then, could I have a furnished house?"}
|
| 67 |
+
{"prompt":"","reply":"I have more of God, they more of the road."}
|
| 68 |
+
{"prompt":"","reply":"It was not always dry land where we dwell."}
|
| 69 |
+
{"prompt":"","reply":"Only that day dawns to which we are awake."}
|
| 70 |
+
{"prompt":"","reply":"What but my fortunes sunk so low in night?"}
|
| 71 |
+
{"prompt":"","reply":"Be it life or death, we crave only reality."}
|
| 72 |
+
{"prompt":"","reply":"For you, for you I am trilling these songs."}
|
| 73 |
+
{"prompt":"","reply":"I think I shall never revisit those scenes."}
|
| 74 |
+
{"prompt":"","reply":"O my breast aches with tender love for all!"}
|
| 75 |
+
{"prompt":"","reply":"That, for the use of the New World, I sing."}
|
| 76 |
+
{"prompt":"","reply":"What shall I learn of beans or beans of me?"}
|
| 77 |
+
{"prompt":"","reply":"But I did not always use this staff of life."}
|
| 78 |
+
{"prompt":"","reply":"I fear my thoughts will not come back to me."}
|
| 79 |
+
{"prompt":"","reply":"O how the immortal phantoms crowd around me!"}
|
| 80 |
+
{"prompt":"","reply":"There was pasture enough for my imagination."}
|
| 81 |
+
{"prompt":"","reply":"This was not the light in which I hoed them."}
|
| 82 |
+
{"prompt":"","reply":"Traveling with me you find what never tires."}
|
| 83 |
+
{"prompt":"","reply":"Ah to sing the song of you, my matron mighty!"}
|
| 84 |
+
{"prompt":"","reply":"But fewer came to see me on trivial business."}
|
| 85 |
+
{"prompt":"","reply":"My practice is “nowhere,” my opinion is here."}
|
| 86 |
+
{"prompt":"","reply":"That’s the greatest thing I have seen to-day."}
|
| 87 |
+
{"prompt":"","reply":"What but my hopes shot upward e’er so bright?"}
|
| 88 |
+
{"prompt":"","reply":"Can we not hire some Abelard to lecture to us?"}
|
| 89 |
+
{"prompt":"","reply":"What is a man anyhow? what am I? what are you?"}
|
| 90 |
+
{"prompt":"","reply":"And proceed to fill my next fold of the future."}
|
| 91 |
+
{"prompt":"","reply":"At six I passed him and his family on the road."}
|
| 92 |
+
{"prompt":"","reply":"Covering all my lands--all my seashores lining!"}
|
| 93 |
+
{"prompt":"","reply":"I doubt if there are three such men in Concord."}
|
| 94 |
+
{"prompt":"","reply":"If our colors are struck and the fighting done?"}
|
| 95 |
+
{"prompt":"","reply":"Nor for myself--my own rebellious self in thee?"}
|
| 96 |
+
{"prompt":"","reply":"O wild and loose to my soul--O wondrous singer!"}
|
| 97 |
+
{"prompt":"","reply":"What think you I take my pen in hand to record?"}
|
| 98 |
+
{"prompt":"","reply":"And these things I see suddenly, what mean they?"}
|
| 99 |
+
{"prompt":"","reply":"I do not wish to quarrel with any man or nation."}
|
| 100 |
+
{"prompt":"","reply":"I had gone down to the woods for other purposes."}
|
| 101 |
+
{"prompt":"","reply":"I went there frequently to observe their habits."}
|
| 102 |
+
{"prompt":"","reply":"Our freedom all in thee! our very lives in thee!"}
|
| 103 |
+
{"prompt":"","reply":"Then my realities; What else is so real as mine?"}
|
| 104 |
+
{"prompt":"","reply":"Thruster holding me tight and that I hold tight!"}
|
| 105 |
+
{"prompt":"","reply":"And you of centuries hence when you listen to me!"}
|
| 106 |
+
{"prompt":"","reply":"Beside, we are sound asleep nearly half our time."}
|
| 107 |
+
{"prompt":"","reply":"The sight of the flames maddens me with pleasure."}
|
| 108 |
+
{"prompt":"","reply":"Why should our life be in any respect provincial?"}
|
| 109 |
+
{"prompt":"","reply":"You too I welcome and fully the same as the rest!"}
|
| 110 |
+
{"prompt":"","reply":"Give me a hammer, and let me feel for the furring."}
|
| 111 |
+
{"prompt":"","reply":"If it would do any good, I would whistle for them."}
|
| 112 |
+
{"prompt":"","reply":"O I at any rate include you all with perfect love!"}
|
| 113 |
+
{"prompt":"","reply":"When I came to build my chimney I studied masonry."}
|
| 114 |
+
{"prompt":"","reply":"But for my part, I preferred the solitary dwelling."}
|
| 115 |
+
{"prompt":"","reply":"By such a pile we may hope to scale heaven at last."}
|
| 116 |
+
{"prompt":"","reply":"For you provided me Washington--and now these also."}
|
| 117 |
+
{"prompt":"","reply":"I am glad that the partridge gets fed, at any rate."}
|
| 118 |
+
{"prompt":"","reply":"I will endeavor to speak a good word for the truth."}
|
| 119 |
+
{"prompt":"","reply":"Its throes will heave our exuviæ from their graves."}
|
| 120 |
+
{"prompt":"","reply":"The very nearness of the fire but cooled our ardor."}
|
| 121 |
+
{"prompt":"","reply":"Trickle Drops Trickle drops! my blue veins leaving!"}
|
| 122 |
+
{"prompt":"","reply":"At present I am a sojourner in civilized life again."}
|
| 123 |
+
{"prompt":"","reply":"Did you find what I sang erewhile so hard to follow?"}
|
| 124 |
+
{"prompt":"","reply":"I did not read books the first summer; I hoed beans."}
|
| 125 |
+
{"prompt":"","reply":"Is it the prophet’s thought I speak, or am I raving?"}
|
| 126 |
+
{"prompt":"","reply":"O I could sing such grandeurs and glories about you!"}
|
| 127 |
+
{"prompt":"","reply":"O to haste firm holding--to haste, haste on with me."}
|
| 128 |
+
{"prompt":"","reply":"They only can force me who obey a higher law than I."}
|
| 129 |
+
{"prompt":"","reply":"Ah my silvery beauty--ah my woolly white and crimson!"}
|
| 130 |
+
{"prompt":"","reply":"Do you think it so easy to have me become your lover?"}
|
| 131 |
+
{"prompt":"","reply":"However, in this case my pains were their own reward."}
|
| 132 |
+
{"prompt":"","reply":"I was witness to events of a less peaceful character."}
|
| 133 |
+
{"prompt":"","reply":"Morning is when I am awake and there is a dawn in me."}
|
| 134 |
+
{"prompt":"","reply":"O highway I travel, do you say to me Do not leave me?"}
|
| 135 |
+
{"prompt":"","reply":"We are determined to be starved before we are hungry."}
|
| 136 |
+
{"prompt":"","reply":"Why should we live with such hurry and waste of life?"}
|
| 137 |
+
{"prompt":"","reply":"I began to comprehend what its inhabitants were about."}
|
| 138 |
+
{"prompt":"","reply":"In the name of these States shall I scorn the antique?"}
|
| 139 |
+
{"prompt":"","reply":"It was as if I had left a cheerful housekeeper behind."}
|
| 140 |
+
{"prompt":"","reply":"The life of the theatre, bar-room, huge hotel, for me!"}
|
| 141 |
+
{"prompt":"","reply":"We speak conformably to the rumor which we have heard."}
|
| 142 |
+
{"prompt":"","reply":"Why do you tremble and clutch my hand so convulsively?"}
|
| 143 |
+
{"prompt":"","reply":"I can find you a Newfoundland dog that will do as much."}
|
| 144 |
+
{"prompt":"","reply":"I thought that there was no need of ice to freeze them."}
|
| 145 |
+
{"prompt":"","reply":"It was one of the most thrilling discords I ever heard."}
|
| 146 |
+
{"prompt":"","reply":"Rather than love, than money, than fame, give me truth."}
|
| 147 |
+
{"prompt":"","reply":"They gave me a pair of her “wings,” which I keep still."}
|
| 148 |
+
{"prompt":"","reply":"I awoke to an answered question, to Nature and daylight."}
|
| 149 |
+
{"prompt":"","reply":"I felt as if I alone of all my townsmen had paid my tax."}
|
| 150 |
+
{"prompt":"","reply":"It reminded me of ghouls and idiots and insane howlings."}
|
| 151 |
+
{"prompt":"","reply":"O you and I! what is it to us what the rest do or think?"}
|
| 152 |
+
{"prompt":"","reply":"The last have nibbled for me a quarter of an acre clean."}
|
| 153 |
+
{"prompt":"","reply":"To a Certain Civilian Did you ask dulcet rhymes from me?"}
|
| 154 |
+
{"prompt":"","reply":"A walk through the woods thither was often my recreation."}
|
| 155 |
+
{"prompt":"","reply":"But soon my homestead was out of their sight and thought."}
|
| 156 |
+
{"prompt":"","reply":"I know not whether it was the dumps or a budding ecstasy."}
|
| 157 |
+
{"prompt":"","reply":"What is that little black thing I see there in the white?"}
|
| 158 |
+
{"prompt":"","reply":"But this spring it broke up more steadily, as I have said."}
|
| 159 |
+
{"prompt":"","reply":"For you these from me, O Democracy, to serve you ma femme!"}
|
| 160 |
+
{"prompt":"","reply":"Or shall I hear the name of Plato and never read his book?"}
|
| 161 |
+
{"prompt":"","reply":"I refer to the degraded poor, not now to the degraded rich."}
|
| 162 |
+
{"prompt":"","reply":"In sane moments we regard only the facts, the case that is."}
|
| 163 |
+
{"prompt":"","reply":"O confine me not! (What is this that frees me so in storms?"}
|
| 164 |
+
{"prompt":"","reply":"What I have observed of the pond is no less true in ethics."}
|
| 165 |
+
{"prompt":"","reply":"By turns our purity inspires and our impurity casts us down."}
|
| 166 |
+
{"prompt":"","reply":"Have we not stood here like trees in the ground long enough?"}
|
| 167 |
+
{"prompt":"","reply":"I have been as sincere a worshipper of Aurora as the Greeks."}
|
| 168 |
+
{"prompt":"","reply":"I say to my friend, Suppose we try who will get there first."}
|
| 169 |
+
{"prompt":"","reply":"If they made their bows of it, we make our gun-stocks of it."}
|
| 170 |
+
{"prompt":"","reply":"O cruel hands that hold me powerless--O helpless soul of me!"}
|
| 171 |
+
{"prompt":"","reply":"Our frigate takes fire, The other asks if we demand quarter?"}
|
| 172 |
+
{"prompt":"","reply":"Who is that intemperate and brutal man whom we would redeem?"}
|
| 173 |
+
{"prompt":"","reply":"Why should I pray? why should I venerate and be ceremonious?"}
|
| 174 |
+
{"prompt":"","reply":"I find it wholesome to be alone the greater part of the time."}
|
| 175 |
+
{"prompt":"","reply":"Nature is as well adapted to our weakness as to our strength."}
|
| 176 |
+
{"prompt":"","reply":"What recommends commerce to me is its enterprise and bravery."}
|
| 177 |
+
{"prompt":"","reply":"For not life’s joys alone I sing, repeating--the joy of death!"}
|
| 178 |
+
{"prompt":"","reply":"I never dreamed of any enormity greater than I have committed."}
|
| 179 |
+
{"prompt":"","reply":"It seems to me more than all the print I have read in my life."}
|
| 180 |
+
{"prompt":"","reply":"Of you O woods and fields! of you strong mountains of my land!"}
|
| 181 |
+
{"prompt":"","reply":"There was a man in my neighborhood who lived in a hollow tree."}
|
| 182 |
+
{"prompt":"","reply":"To have the feeling to-day or any day I am sufficient as I am."}
|
| 183 |
+
{"prompt":"","reply":"I answer that he cannot without disgrace be associated with it."}
|
| 184 |
+
{"prompt":"","reply":"I do not flatter myself, but if it be possible they flatter me."}
|
| 185 |
+
{"prompt":"","reply":"I think that he must be the man of the most faith of any alive."}
|
| 186 |
+
{"prompt":"","reply":"It comes on apace; my sumachs and sweet-briers tremble.—Eh, Mr."}
|
| 187 |
+
{"prompt":"","reply":"O I mourn and yet exult, I am rapt with love for all, Pioneers!"}
|
| 188 |
+
{"prompt":"","reply":"Prodigal, you have given me love--therefore I to you give love!"}
|
| 189 |
+
{"prompt":"","reply":"But we love better to talk about it: that we say is our mission."}
|
| 190 |
+
{"prompt":"","reply":"I could not but notice some of the peculiarities of my visitors."}
|
| 191 |
+
{"prompt":"","reply":"In Arcadia, when I was there, I did not see any hammering stone."}
|
| 192 |
+
{"prompt":"","reply":"O night! do I not see my love fluttering out among the breakers?"}
|
| 193 |
+
{"prompt":"","reply":"The value of a man is not in his skin, that we should touch him."}
|
| 194 |
+
{"prompt":"","reply":"To have gone forth among other Gods, these men and women I love."}
|
| 195 |
+
{"prompt":"","reply":"What, to passions I witness around me to-day? was the sea risen?"}
|
| 196 |
+
{"prompt":"","reply":"Why when they leave me do my pennants of joy sink flat and lank?"}
|
| 197 |
+
{"prompt":"","reply":"I am thankful that this pond was made deep and pure for a symbol."}
|
| 198 |
+
{"prompt":"","reply":"I have loved many women and men, but I love none better than you."}
|
| 199 |
+
{"prompt":"","reply":"The other side of the globe is but the home of our correspondent."}
|
| 200 |
+
{"prompt":"","reply":"They were the voices of old burghers that I heard in the streets."}
|
| 201 |
+
{"prompt":"","reply":"Yet I sustained myself by the prospect of such reading in future."}
|
| 202 |
+
{"prompt":"","reply":"Do you think the friendship of me would be unalloy’d satisfaction?"}
|
| 203 |
+
{"prompt":"","reply":"He was only a little more weather-beaten than when I saw him last."}
|
| 204 |
+
{"prompt":"","reply":"I give the Latin on account of the savoriness of the trivial name."}
|
| 205 |
+
{"prompt":"","reply":"I know of nothing more purgative of winter fumes and indigestions."}
|
| 206 |
+
{"prompt":"","reply":"I roll myself upon you as upon a bed, I resign myself to the dusk."}
|
| 207 |
+
{"prompt":"","reply":"Never must you be divided, in our ranks you move united, Pioneers!"}
|
| 208 |
+
{"prompt":"","reply":"Were mankind murderous or jealous upon you, my brother, my sister?"}
|
| 209 |
+
{"prompt":"","reply":"But all this is very selfish, I have heard some of my townsmen say."}
|
| 210 |
+
{"prompt":"","reply":"I will only hint at some of the enterprises which I have cherished."}
|
| 211 |
+
{"prompt":"","reply":"Why should we exaggerate any one kind at the expense of the others?"}
|
| 212 |
+
{"prompt":"","reply":"And how shall I deck my song for the large sweet soul that has gone?"}
|
| 213 |
+
{"prompt":"","reply":"Earth of the limpid gray of clouds brighter and clearer for my sake!"}
|
| 214 |
+
{"prompt":"","reply":"I came to love my rows, my beans, though so many more than I wanted."}
|
| 215 |
+
{"prompt":"","reply":"I sacrificed it to Vulcan, for it was past serving the god Terminus."}
|
| 216 |
+
{"prompt":"","reply":"It is as precious to us as it was to our Saxon and Norman ancestors."}
|
| 217 |
+
{"prompt":"","reply":"Then upon the march we fittest die, soon and sure the gap is fill’d."}
|
| 218 |
+
{"prompt":"","reply":"Under what latitudes reside the heathen to whom we would send light?"}
|
| 219 |
+
{"prompt":"","reply":"Even while you should think you had unquestionably caught me, behold!"}
|
| 220 |
+
{"prompt":"","reply":"I speak the pass-word primeval, I give the sign of democracy, By God!"}
|
| 221 |
+
{"prompt":"","reply":"It is for no particular item in the tax-bill that I refuse to pay it."}
|
| 222 |
+
{"prompt":"","reply":"Perhaps the one I want so much will rise, will rise with some of you."}
|
| 223 |
+
{"prompt":"","reply":"We are acquainted with a mere pellicle of the globe on which we live."}
|
| 224 |
+
{"prompt":"","reply":"But if we stay at home and mind our business, who will want railroads?"}
|
| 225 |
+
{"prompt":"","reply":"I cannot be discharged from you! not from one any sooner than another!"}
|
| 226 |
+
{"prompt":"","reply":"Or I heard the peep of the young when I could not see the parent bird."}
|
| 227 |
+
{"prompt":"","reply":"Shall we forever resign the pleasure of construction to the carpenter?"}
|
| 228 |
+
{"prompt":"","reply":"Flag of death! (how I watch’d you through the smoke of battle pressing!"}
|
| 229 |
+
{"prompt":"","reply":"I was never molested by any person but those who represented the state."}
|
| 230 |
+
{"prompt":"","reply":"In this respect, my company was winnowed by my mere distance from town."}
|
| 231 |
+
{"prompt":"","reply":"Neither did I put any sal soda, or other acid or alkali, into my bread."}
|
| 232 |
+
{"prompt":"","reply":"They are not exhalations like our daily colloquies and vaporous breath."}
|
| 233 |
+
{"prompt":"","reply":"But for the most part it is as solitary where I live as on the prairies."}
|
| 234 |
+
{"prompt":"","reply":"Hasten throat and sound your last, Salute me--salute the days once more."}
|
| 235 |
+
{"prompt":"","reply":"I hasten to inform him or her it is just as lucky to die, and I know it."}
|
| 236 |
+
{"prompt":"","reply":"Some I have seen, sixty or a hundred feet long and thirty feet broad...."}
|
| 237 |
+
{"prompt":"","reply":"An old forest fence which had seen its best days was a great haul for me."}
|
| 238 |
+
{"prompt":"","reply":"I have been inside one or two of them, and know what they are lined with."}
|
| 239 |
+
{"prompt":"","reply":"I think that there are very few important communications made through it."}
|
| 240 |
+
{"prompt":"","reply":"I would drink deeper; fish in the sky, whose bottom is pebbly with stars."}
|
| 241 |
+
{"prompt":"","reply":"Now I absorb immortality and peace, I admire death and test propositions."}
|
| 242 |
+
{"prompt":"","reply":"To a Foil’d European Revolutionaire Courage yet, my brother or my sister!"}
|
| 243 |
+
{"prompt":"","reply":"After all our discoveries and inventions no man will go by a pile of wood."}
|
| 244 |
+
{"prompt":"","reply":"And you each and everywhere whom I specify not, but include just the same!"}
|
| 245 |
+
{"prompt":"","reply":"I see where the breeze dashes across it by the streaks or flakes of light."}
|
| 246 |
+
{"prompt":"","reply":"Let me see new ones every day--let me hold new ones by the hand every day!"}
|
| 247 |
+
{"prompt":"","reply":"The wildness and adventure that are in fishing still recommended it to me."}
|
| 248 |
+
{"prompt":"","reply":"Clear and sweet is my soul, and clear and sweet is all that is not my soul."}
|
| 249 |
+
{"prompt":"","reply":"I am larger, better than I thought, I did not know I held so much goodness."}
|
| 250 |
+
{"prompt":"","reply":"I have always been regretting that I was not as wise as the day I was born."}
|
| 251 |
+
{"prompt":"","reply":"I have water from the spring, and a loaf of brown bread on the shelf.—Hark!"}
|
| 252 |
+
{"prompt":"","reply":"Now only do I know you, O fair auroral skies--O morning dew upon the grass!"}
|
| 253 |
+
{"prompt":"","reply":"The Merlin it seemed to me it might be called: but I care not for its name."}
|
| 254 |
+
{"prompt":"","reply":"Through this, whistling a tune, we took our way to the haunts of men again."}
|
| 255 |
+
{"prompt":"","reply":"Were I to you as the boss employing and paying you, would that satisfy you?"}
|
| 256 |
+
{"prompt":"","reply":"Yet we esteem ourselves wise, and have an established order on the surface."}
|
| 257 |
+
{"prompt":"","reply":"Have we not grovel’d here long enough, eating and drinking like mere brutes?"}
|
| 258 |
+
{"prompt":"","reply":"I am not responsible for the successful working of the machinery of society."}
|
| 259 |
+
{"prompt":"","reply":"I remember when wages were sixty cents a day for laborers on this very road."}
|
| 260 |
+
{"prompt":"","reply":"Or I guess the grass is itself a child, the produced babe of the vegetation."}
|
| 261 |
+
{"prompt":"","reply":"They tell me of California and Texas, of England and the Indies, of the Hon."}
|
| 262 |
+
{"prompt":"","reply":"Where I lived was as far off as many a region viewed nightly by astronomers."}
|
| 263 |
+
{"prompt":"","reply":"Forms, colors, densities, odors--what is it in me that corresponds with them?"}
|
| 264 |
+
{"prompt":"","reply":"The splendors of the past day? or the splendor of the night that envelops me?"}
|
| 265 |
+
{"prompt":"","reply":"Behold, I do not give lectures or a little charity, When I give I give myself."}
|
| 266 |
+
{"prompt":"","reply":"Do you see no further than this facade, this smooth and tolerant manner of me?"}
|
| 267 |
+
{"prompt":"","reply":"I accept Reality and dare not question it, Materialism first and last imbuing."}
|
| 268 |
+
{"prompt":"","reply":"I was sometimes tempted to stretch an awning over them and take my seat there."}
|
| 269 |
+
{"prompt":"","reply":"I would fain keep sober always; and there are infinite degrees of drunkenness."}
|
| 270 |
+
{"prompt":"","reply":"The style, the house and grounds and “entertainment” pass for nothing with me."}
|
| 271 |
+
{"prompt":"","reply":"Through our own recovered innocence we discern the innocence of our neighbors."}
|
| 272 |
+
{"prompt":"","reply":"As for the rest of my readers, they will accept such portions as apply to them."}
|
| 273 |
+
{"prompt":"","reply":"First I take an axe and pail and go in search of water, if that be not a dream."}
|
| 274 |
+
{"prompt":"","reply":"He manœuvred so cunningly that I could not get within half a dozen rods of him."}
|
| 275 |
+
{"prompt":"","reply":"I hear the chorus, it is a grand opera, Ah this indeed is music--this suits me."}
|
| 276 |
+
{"prompt":"","reply":"I tasted them out of compliment to Nature, though they were scarcely palatable."}
|
| 277 |
+
{"prompt":"","reply":"It can have no pure right over my person and property but what I concede to it."}
|
| 278 |
+
{"prompt":"","reply":"Methinks I was nearly in this frame of mind; the world lay about at this angle."}
|
| 279 |
+
{"prompt":"","reply":"So I came in, and shut the door, and passed my first spring night in the woods."}
|
| 280 |
+
{"prompt":"","reply":"What is there more, that I lag and pause and crouch extended with unshut mouth?"}
|
| 281 |
+
{"prompt":"","reply":"Wrench’d and sweaty--calm and cool then my body becomes, I sleep--I sleep long."}
|
| 282 |
+
{"prompt":"","reply":"For I purposely talked to him as if he were a philosopher, or desired to be one."}
|
| 283 |
+
{"prompt":"","reply":"I guess it must be the flag of my disposition, out of hopeful green stuff woven."}
|
| 284 |
+
{"prompt":"","reply":"I raised the glass, and he went off over the window-sill in that crippled state."}
|
| 285 |
+
{"prompt":"","reply":"If we suppose him to pay a rent instead, this is but a doubtful choice of evils."}
|
| 286 |
+
{"prompt":"","reply":"It is a little star-dust caught, a segment of the rainbow which I have clutched."}
|
| 287 |
+
{"prompt":"","reply":"Most ponds, emptied, would leave a meadow no more hollow than we frequently see."}
|
| 288 |
+
{"prompt":"","reply":"Sound out, voices of young men! loudly and musically call me by my nighest name!"}
|
| 289 |
+
{"prompt":"","reply":"Also, our sentences wanted room to unfold and form their columns in the interval."}
|
| 290 |
+
{"prompt":"","reply":"I confess that I have hitherto indulged very little in philanthropic enterprises."}
|
| 291 |
+
{"prompt":"","reply":"In our climate, in the summer, it was formerly almost solely a covering at night."}
|
| 292 |
+
{"prompt":"","reply":"O, they swarm; the sun is too warm there; they are born too far into life for me."}
|
| 293 |
+
{"prompt":"","reply":"Was thy existence then too fanciful For our life’s common light, who are so dull?"}
|
| 294 |
+
{"prompt":"","reply":"For human society I was obliged to conjure up the former occupants of these woods."}
|
| 295 |
+
{"prompt":"","reply":"I have actually fished from the same kind of necessity that the first fishers did."}
|
| 296 |
+
{"prompt":"","reply":"Moreover, when at the pond, I wished sometimes to add fish to my fare for variety."}
|
| 297 |
+
{"prompt":"","reply":"To be absolv’d from previous ties and conventions, I from mine and you from yours!"}
|
| 298 |
+
{"prompt":"","reply":"I like sometimes to take rank hold on life and spend my day more as the animals do."}
|
| 299 |
+
{"prompt":"","reply":"I pursued with a paddle and he dived, but when he came up I was nearer than before."}
|
| 300 |
+
{"prompt":"","reply":"It was I and Fire that lived there; and commonly my housekeeper proved trustworthy."}
|
| 301 |
+
{"prompt":"","reply":"That I could forget the trickling tears and the blows of the bludgeons and hammers!"}
|
| 302 |
+
{"prompt":"","reply":"The saloon of the steamer! the crowded excursion for me! the torchlight procession!"}
|
| 303 |
+
{"prompt":"","reply":"When I opened my door in the evening, off they would go with a squeak and a bounce."}
|
| 304 |
+
{"prompt":"","reply":"While my sight that was bound in my eyes unclosed, As to long panoramas of visions."}
|
| 305 |
+
{"prompt":"","reply":"Hands I have taken, face I have kiss’d, mortal I have ever touch’d, it shall be you."}
|
| 306 |
+
{"prompt":"","reply":"I have, as it were, my own sun and moon and stars, and a little world all to myself."}
|
| 307 |
+
{"prompt":"","reply":"It was a lake of rainbow light, in which, for a short while, I lived like a dolphin."}
|
| 308 |
+
{"prompt":"","reply":"O it is the shape, the shape of my mate.’ O moon do not keep her from me any longer."}
|
| 309 |
+
{"prompt":"","reply":"But as it was, I considered that I enhanced the value of the land by squatting on it."}
|
| 310 |
+
{"prompt":"","reply":"I drink at it; but while I drink I see the sandy bottom and detect how shallow it is."}
|
| 311 |
+
{"prompt":"","reply":"I may be either the drift-wood in the stream, or Indra in the sky looking down on it."}
|
| 312 |
+
{"prompt":"","reply":"If I were to preach at all in this strain, I should say rather, Set about being good."}
|
| 313 |
+
{"prompt":"","reply":"O the bleeding drops of red, Where on the deck my Captain lies, Fallen cold and dead."}
|
| 314 |
+
{"prompt":"","reply":"Pierce the woods, the earth, Somewhere listening to catch you must be the one I want."}
|
| 315 |
+
{"prompt":"","reply":"Strange and hard that paradox true I give, Objects gross and the unseen soul are one."}
|
| 316 |
+
{"prompt":"","reply":"Why level downward to our dullest perception always, and praise that as common sense?"}
|
| 317 |
+
{"prompt":"","reply":"I concluded that he laughed in derision of my efforts, confident of his own resources."}
|
| 318 |
+
{"prompt":"","reply":"I see the heroes at other toils, I see well-wielded in their hands the better weapons."}
|
| 319 |
+
{"prompt":"","reply":"It is true, I fear that others may have fallen into it, and so helped to keep it open."}
|
| 320 |
+
{"prompt":"","reply":"Not I, not any one else can travel that road for you, You must travel it for yourself."}
|
| 321 |
+
{"prompt":"","reply":"They plainly did not know how to treat me, but behaved like persons who are underbred."}
|
| 322 |
+
{"prompt":"","reply":"What is more subtle than this which ties me to the woman or man that looks in my face?"}
|
| 323 |
+
{"prompt":"","reply":"Why are there men and women that while they are nigh me the sunlight expands my blood?"}
|
| 324 |
+
{"prompt":"","reply":"I will not have my eyes put out and my ears spoiled by its smoke and steam and hissing."}
|
| 325 |
+
{"prompt":"","reply":"Well I have, for the Fourth-month showers have, and the mica on the side of a rock has."}
|
| 326 |
+
{"prompt":"","reply":"At one time, owing to the emptiness of my purse, I saw none of it for more than a month."}
|
| 327 |
+
{"prompt":"","reply":"I cannot believe that our factory system is the best mode by which men may get clothing."}
|
| 328 |
+
{"prompt":"","reply":"I fear that it may enjoy a certain health of its own; that we may be well, yet not pure."}
|
| 329 |
+
{"prompt":"","reply":"I was actually afraid that I might by that time be doing what is called a good business."}
|
| 330 |
+
{"prompt":"","reply":"What is known I strip away, I launch all men and women forward with me into the Unknown."}
|
| 331 |
+
{"prompt":"","reply":"Did thy bright gleam mysterious converse hold With our congenial souls? secrets too bold?"}
|
| 332 |
+
{"prompt":"","reply":"I cannot say to any person what I hear--I cannot say it to myself-- it is very wonderful."}
|
| 333 |
+
{"prompt":"","reply":"I have found that no exertion of the legs can bring two minds much nearer to one another."}
|
| 334 |
+
{"prompt":"","reply":"It was fit that I should live on rice, mainly, who loved so well the philosophy of India."}
|
| 335 |
+
{"prompt":"","reply":"This experience entitled me to be regarded as a sort of real-estate broker by my friends."}
|
| 336 |
+
{"prompt":"","reply":"When a man is warmed by the several modes which I have described, what does he want next?"}
|
| 337 |
+
{"prompt":"","reply":"Why art thou banished from our hearth and hall, Thou who art welcomed and beloved by all?"}
|
| 338 |
+
{"prompt":"","reply":"All memorable events, I should say, transpire in morning time and in a morning atmosphere."}
|
| 339 |
+
{"prompt":"","reply":"I never chanced to see its kind in any market; it would be the cynosure of all eyes there."}
|
| 340 |
+
{"prompt":"","reply":"I told them that I drank at the pond, and pointed thither, offering to lend them a dipper."}
|
| 341 |
+
{"prompt":"","reply":"Something I cannot see puts upward libidinous prongs, Seas of bright juice suffuse heaven."}
|
| 342 |
+
{"prompt":"","reply":"This minute that comes to me over the past decillions, There is no better than it and now."}
|
| 343 |
+
{"prompt":"","reply":"We are so degraded that we cannot speak simply of the necessary functions of human nature."}
|
| 344 |
+
{"prompt":"","reply":"But I would say to my fellows, once for all, As long as possible live free and uncommitted."}
|
| 345 |
+
{"prompt":"","reply":"But to tell the truth, I find myself at present somewhat less particular in these respects."}
|
| 346 |
+
{"prompt":"","reply":"For the most part we allow only outlying and transient circumstances to make our occasions."}
|
| 347 |
+
{"prompt":"","reply":"It was not so much within doors as behind a door where I sat, even in the rainiest weather."}
|
| 348 |
+
{"prompt":"","reply":"Or leave me here as now, Dull, parrot-like and old, with crack’d voice harping, screeching?"}
|
| 349 |
+
{"prompt":"","reply":"Were I as the head teacher, charitable proprietor, wise statesman, what would it amount to?"}
|
| 350 |
+
{"prompt":"","reply":"For it the mystic evolution, Not the right only justified, what we call evil also justified."}
|
| 351 |
+
{"prompt":"","reply":"I beat and pound for the dead, I blow through my embouchures my loudest and gayest for them."}
|
| 352 |
+
{"prompt":"","reply":"I thought, as I have my living to get, and have not eaten to-day, that I might go a-fishing."}
|
| 353 |
+
{"prompt":"","reply":"Low hangs the moon, it rose late, It is lagging--O I think it is heavy with love, with love."}
|
| 354 |
+
{"prompt":"","reply":"Our inventions are wont to be pretty toys, which distract our attention from serious things."}
|
| 355 |
+
{"prompt":"","reply":"The only obligation which I have a right to assume, is to do at any time what I think right."}
|
| 356 |
+
{"prompt":"","reply":"Thus was my first year’s life in the woods completed; and the second year was similar to it."}
|
| 357 |
+
{"prompt":"","reply":"I did nor for a moment feel confined, and the walls seemed a great waste of stone and mortar."}
|
| 358 |
+
{"prompt":"","reply":"I walked about the outside, at first unobserved from within, the window was so deep and high."}
|
| 359 |
+
{"prompt":"","reply":"The gods must be proud, thought I, with such forked flashes to rout a poor unarmed fisherman."}
|
| 360 |
+
{"prompt":"","reply":"Yet I have no doubt that that people’s rulers are as wise as the average of civilized rulers."}
|
| 361 |
+
{"prompt":"","reply":"Hence from my shuddering sight to never more return that show of blacken’d, mutilated corpses!"}
|
| 362 |
+
{"prompt":"","reply":"I behold the picturesque giant and love him, and I do not stop there, I go with the team also."}
|
| 363 |
+
{"prompt":"","reply":"I have found it a singular luxury to talk across the pond to a companion on the opposite side."}
|
| 364 |
+
{"prompt":"","reply":"Immense have been the preparations for me, Faithful and friendly the arms that have help’d me."}
|
| 365 |
+
{"prompt":"","reply":"So our human life but dies down to its root, and still puts forth its green blade to eternity."}
|
| 366 |
+
{"prompt":"","reply":"The wonder is how they, how you and I, can live this slimy, beastly life, eating and drinking."}
|
| 367 |
+
{"prompt":"","reply":"While I enjoy the friendship of the seasons I trust that nothing can make life a burden to me."}
|
| 368 |
+
{"prompt":"","reply":"All I mark as my own you shall offset it with your own, Else it were time lost listening to me."}
|
| 369 |
+
{"prompt":"","reply":"How large a body of Walden water would be required to reflect a green tint I have never proved."}
|
| 370 |
+
{"prompt":"","reply":"I see your rounded never-erased flow, I see ’neath the rims of your haggard and mean disguises."}
|
| 371 |
+
{"prompt":"","reply":"Most men I do not meet at all, for they seem not to have time; they are busy about their beans."}
|
| 372 |
+
{"prompt":"","reply":"There is an incessant influx of novelty into the world, and yet we tolerate incredible dulness."}
|
| 373 |
+
{"prompt":"","reply":"Were all educations practical and ornamental well display’d out of me, what would it amount to?"}
|
| 374 |
+
{"prompt":"","reply":"Answer me these questions, and then perhaps I may look at your bawbles and find them ornamental."}
|
| 375 |
+
{"prompt":"","reply":"I have always endeavored to acquire strict business habits; they are indispensable to every man."}
|
| 376 |
+
{"prompt":"","reply":"Let the reports of all the learned societies come to us, and we will see if they know any thing."}
|
| 377 |
+
{"prompt":"","reply":"One very dark night I directed thus on their way two young men who had been fishing in the pond."}
|
| 378 |
+
{"prompt":"","reply":"Shall we always study to obtain more of these things, and not sometimes to be content with less?"}
|
| 379 |
+
{"prompt":"","reply":"The learn’d, virtuous, benevolent, and the usual terms, A man like me and never the usual terms."}
|
| 380 |
+
{"prompt":"","reply":"This sight reminded me of falconry and what nobleness and poetry are associated with that sport."}
|
| 381 |
+
{"prompt":"","reply":"A phœbe soon built in my shed, and a robin for protection in a pine which grew against the house."}
|
| 382 |
+
{"prompt":"","reply":"Broad muscular fields, branches of live oak, loving lounger in my winding paths, it shall be you!"}
|
| 383 |
+
{"prompt":"","reply":"I do not call one greater and one smaller, That which fills its period and place is equal to any."}
|
| 384 |
+
{"prompt":"","reply":"I need only suggest what kind of sermons are still listened to in the most enlightened countries."}
|
| 385 |
+
{"prompt":"","reply":"I warn you, mothers, that my sympathies do not always make the usual phil-anthropic distinctions."}
|
| 386 |
+
{"prompt":"","reply":"No shutter’d room or school can commune with me, But roughs and little children better than they."}
|
| 387 |
+
{"prompt":"","reply":"Some have asked what I got to eat; if I did not feel lonesome; if I was not afraid; and the like."}
|
| 388 |
+
{"prompt":"","reply":"What I have to do is to see, at any rate, that I do not lend myself to the wrong which I condemn."}
|
| 389 |
+
{"prompt":"","reply":"As I did not teach for the good of my fellow-men, but simply for a livelihood, this was a failure."}
|
| 390 |
+
{"prompt":"","reply":"He whom I call answers me and takes the place of my lover, He rises with me silently from the bed."}
|
| 391 |
+
{"prompt":"","reply":"I say, beware of all enterprises that require new clothes, and not rather a new wearer of clothes."}
|
| 392 |
+
{"prompt":"","reply":"Not that I am less humane than others, but I did not perceive that my feelings were much affected."}
|
| 393 |
+
{"prompt":"","reply":"This frame, so slightly clad, was a sort of crystallization around me, and reacted on the builder."}
|
| 394 |
+
{"prompt":"","reply":"What Champollion will decipher this hieroglyphic for us, that we may turn over a new leaf at last?"}
|
| 395 |
+
{"prompt":"","reply":"I fly those flights of a fluid and swallowing soul, My course runs below the soundings of plummets."}
|
| 396 |
+
{"prompt":"","reply":"I see poor crazed Lucia’s eyes’ unnatural gleam, Her hair down her back falls loose and dishevel’d."}
|
| 397 |
+
{"prompt":"","reply":"I wonder where they get those tokens, Did I pass that way huge times ago and negligently drop them?"}
|
| 398 |
+
{"prompt":"","reply":"The volatile truth of our words should continually betray the inadequacy of the residual statement."}
|
| 399 |
+
{"prompt":"","reply":"What was the meaning of this so steady and self-respecting, this small Herculean labor, I knew not."}
|
| 400 |
+
{"prompt":"","reply":"Ah more than any priest O soul we too believe in God, But with the mystery of God we dare not dally."}
|
| 401 |
+
{"prompt":"","reply":"Before his house was pulled down, when his comrades avoided it as “an unlucky castle,” I visited it."}
|
| 402 |
+
{"prompt":"","reply":"Demon or bird! (said the boy’s soul,) Is it indeed toward your mate you sing? or is it really to me?"}
|
| 403 |
+
{"prompt":"","reply":"I have made some sacrifices to a sense of duty, and among others have sacrificed this pleasure also."}
|
| 404 |
+
{"prompt":"","reply":"I usually go to the village along its causeway, and am, as it were, related to society by this link."}
|
| 405 |
+
{"prompt":"","reply":"O tender and fierce pangs, I can stand them not, I will depart; O to be a Virginian where I grew up!"}
|
| 406 |
+
{"prompt":"","reply":"I live in the angle of a leaden wall, into whose composition was poured a little alloy of bell metal."}
|
| 407 |
+
{"prompt":"","reply":"I must follow up these continual lessons of the air, water, earth, I perceive I have no time to lose."}
|
| 408 |
+
{"prompt":"","reply":"I see the nameless masonries, venerable messages of the unknown events, heroes, records of the earth."}
|
| 409 |
+
{"prompt":"","reply":"Or I could refer you to Ireland, which is marked as one of the white or enlightened spots on the map."}
|
| 410 |
+
{"prompt":"","reply":"We need to witness our own limits transgressed, and some life pasturing freely where we never wander."}
|
| 411 |
+
{"prompt":"","reply":"As for adopting the ways which the State has provided for remedying the evil, I know not of such ways."}
|
| 412 |
+
{"prompt":"","reply":"But sing poet in our name, Sing of the love we bore him--because you, dweller in camps, know it truly."}
|
| 413 |
+
{"prompt":"","reply":"For my part, I should like to know who in those days did not build them,—who were above such trifling."}
|
| 414 |
+
{"prompt":"","reply":"I do not wish to split hairs, to make fine distinctions, or set myself up as better than my neighbors."}
|
| 415 |
+
{"prompt":"","reply":"It was the natural yearning of that portion of our most primitive ancestor which still survived in us."}
|
| 416 |
+
{"prompt":"","reply":"The youngster and the red-faced girl turn aside up the bushy hill, I peeringly view them from the top."}
|
| 417 |
+
{"prompt":"","reply":"We boast that we belong to the nineteenth century and are making the most rapid strides of any nation."}
|
| 418 |
+
{"prompt":"","reply":"Flint’s, or Sandy Pond, in Lincoln, our greatest lake and inland sea, lies about a mile east of Walden."}
|
| 419 |
+
{"prompt":"","reply":"He brought his own knife, though I had two, and we used to scour them by thrusting them into the earth."}
|
| 420 |
+
{"prompt":"","reply":"I do not value chiefly a man’s uprightness and benevolence, which are, as it were, his stem and leaves."}
|
| 421 |
+
{"prompt":"","reply":"I now first began to inhabit my house, I may say, when I began to use it for warmth as well as shelter."}
|
| 422 |
+
{"prompt":"","reply":"Is not our own interior white on the chart? black though it may prove, like the coast, when discovered."}
|
| 423 |
+
{"prompt":"","reply":"The one who came from farthest to my lodge, through deepest snows and most dismal tempests, was a poet."}
|
| 424 |
+
{"prompt":"","reply":"What do you suppose I would intimate to you in a hundred ways, but that man or woman is as good as God?"}
|
| 425 |
+
{"prompt":"","reply":"But I was at the same time conscious of a slight insanity in my mood, and seemed to foresee my recovery."}
|
| 426 |
+
{"prompt":"","reply":"Double yourself and receive me darkness, Receive me and my lover too, he will not let me go without him."}
|
| 427 |
+
{"prompt":"","reply":"However, the government does not concern me much, and I shall bestow the fewest possible thoughts on it."}
|
| 428 |
+
{"prompt":"","reply":"I do not snivel that snivel the world over, That months are vacuums and the ground but wallow and filth."}
|
| 429 |
+
{"prompt":"","reply":"I too am not a bit tamed, I too am untranslatable, I sound my barbaric yawp over the roofs of the world."}
|
| 430 |
+
{"prompt":"","reply":"O give me the clue! (it lurks in the night here somewhere,) O if I am to have so much, let me have more!"}
|
| 431 |
+
{"prompt":"","reply":"Some of you, we all know, are poor, find it hard to live, are sometimes, as it were, gasping for breath."}
|
| 432 |
+
{"prompt":"","reply":"I am he that walks with the tender and growing night, I call to the earth and sea half-held by the night."}
|
| 433 |
+
{"prompt":"","reply":"If I have got to drag my trap, I will take care that it be a light one and do not nip me in a vital part."}
|
| 434 |
+
{"prompt":"","reply":"Not physiognomy alone, nor brain alone, is worthy for the Muse;--I say the Form complete is worthier far."}
|
| 435 |
+
{"prompt":"","reply":"They told me that they had some in the ice-houses at Fresh Pond five years old which was as good as ever."}
|
| 436 |
+
{"prompt":"","reply":"While I was surveying, the ice, which was sixteen inches thick, undulated under a slight wind like water."}
|
| 437 |
+
{"prompt":"","reply":"But I retained the landscape, and I have since annually carried off what it yielded without a wheelbarrow."}
|
| 438 |
+
{"prompt":"","reply":"I cannot aid with my wringing fingers, I can but rush to the surf and let it drench me and freeze upon me."}
|
| 439 |
+
{"prompt":"","reply":"I do not wish to flatter my townsmen, nor to be flattered by them, for that will not advance either of us."}
|
| 440 |
+
{"prompt":"","reply":"I tried flour also; but have at last found a mixture of rye and Indian meal most convenient and agreeable."}
|
| 441 |
+
{"prompt":"","reply":"Nutting had a famous fox-hound named Burgoyne,—he pronounced it Bugine,—which my informant used to borrow."}
|
| 442 |
+
{"prompt":"","reply":"Then we burst forth, we float, In Time and Space O soul, prepared for them, Equal, equipt at last, (O joy!"}
|
| 443 |
+
{"prompt":"","reply":"To Old Age I see in you the estuary that enlarges and spreads itself grandly as it pours in the great sea."}
|
| 444 |
+
{"prompt":"","reply":"Yet till this is otherwise we are not civilized, and, if gentlemen and ladies, are not true men and women."}
|
| 445 |
+
{"prompt":"","reply":"I cast my vote, perchance, as I think right; but I am not vitally concerned that that right should prevail."}
|
| 446 |
+
{"prompt":"","reply":"I had more visitors while I lived in the woods than at any other period in my life; I mean that I had some."}
|
| 447 |
+
{"prompt":"","reply":"It has a sweetish taste, much like that of a frostbitten potato, and I found it better boiled than roasted."}
|
| 448 |
+
{"prompt":"","reply":"Sometimes in the twilight I alternately lost and recovered sight of one sitting motionless under my window."}
|
| 449 |
+
{"prompt":"","reply":"But in other directions, even from this point, I could not see over or beyond the woods which surrounded me."}
|
| 450 |
+
{"prompt":"","reply":"I but use you a minute, then I resign you, stallion, Why do I need your paces when I myself out-gallop them?"}
|
| 451 |
+
{"prompt":"","reply":"In imagination I have bought all the farms in succession, for all were to be bought, and I knew their price."}
|
| 452 |
+
{"prompt":"","reply":"The press of my foot to the earth springs a hundred affections, They scorn the best I can do to relate them."}
|
| 453 |
+
{"prompt":"","reply":"A tenor large and fresh as the creation fills me, The orbic flex of his mouth is pouring and filling me full."}
|
| 454 |
+
{"prompt":"","reply":"I got up early and bathed in the pond; that was a religious exercise, and one of the best things which I did."}
|
| 455 |
+
{"prompt":"","reply":"It convinces me that Earth is still in her swaddling clothes, and stretches forth baby fingers on every side."}
|
| 456 |
+
{"prompt":"","reply":"The fruits eaten temperately need not make us ashamed of our appetites, nor interrupt the worthiest pursuits."}
|
| 457 |
+
{"prompt":"","reply":"A word of the faith that never balks, Here or henceforward it is all the same to me, I accept Time absolutely."}
|
| 458 |
+
{"prompt":"","reply":"I called on the king, but he made me wait in his hall, and conducted like a man incapacitated for hospitality."}
|
| 459 |
+
{"prompt":"","reply":"I had no lock nor bolt but for the desk which held my papers, not even a nail to put over my latch or windows."}
|
| 460 |
+
{"prompt":"","reply":"It is no dream of mine, To ornament a line; I cannot come nearer to God and Heaven Than I live to Walden even."}
|
| 461 |
+
{"prompt":"","reply":"Perhaps it seemed to me that I had several more lives to live, and could not spare any more time for that one."}
|
| 462 |
+
{"prompt":"","reply":"The first question which I am tempted to put to the proprietor of such great impropriety is, Who bolsters you?"}
|
| 463 |
+
{"prompt":"","reply":"While civilization has been improving our houses, it has not equally improved the men who are to inhabit them."}
|
| 464 |
+
{"prompt":"","reply":"Also, as I have said, the bubbles themselves within the ice operate as burning-glasses to melt the ice beneath."}
|
| 465 |
+
{"prompt":"","reply":"I came into this world, not chiefly to make this a good place to live in, but to live in it, be it good or bad."}
|
| 466 |
+
{"prompt":"","reply":"I have seen the he-bird also, I have paus’d to hear him near at hand inflating his throat and joyfully singing."}
|
| 467 |
+
{"prompt":"","reply":"Many an irksome noise, go a long way off, is heard as music, a proud sweet satire on the meanness of our lives."}
|
| 468 |
+
{"prompt":"","reply":"The shower was now over, and a rainbow above the eastern woods promised a fair evening; so I took my departure."}
|
| 469 |
+
{"prompt":"","reply":"How long shall we sit in our porticoes practising idle and musty virtues, which any work would make impertinent?"}
|
| 470 |
+
{"prompt":"","reply":"I trust that none will stretch the seams in putting on the coat, for it may do good service to him whom it fits."}
|
| 471 |
+
{"prompt":"","reply":"If we respected only what is inevitable and has a right to be, music and poetry would resound along the streets."}
|
| 472 |
+
{"prompt":"","reply":"We should feed and clothe him gratuitously sometimes, and recruit him with our cordials, before we judge of him."}
|
| 473 |
+
{"prompt":"","reply":"I am far from supposing that my case is a peculiar one; no doubt many of my readers would make a similar defence."}
|
| 474 |
+
{"prompt":"","reply":"If we live in the nineteenth century, why should we not enjoy the advantages which the nineteenth century offers?"}
|
| 475 |
+
{"prompt":"","reply":"The true harvest of my daily life is somewhat as intangible and indescribable as the tints of morning or evening."}
|
| 476 |
+
{"prompt":"","reply":"I set out one afternoon to go a-fishing to Fair-Haven, through the woods, to eke out my scanty fare of vegetables."}
|
| 477 |
+
{"prompt":"","reply":"It is not necessary that a man should earn his living by the sweat of his brow, unless he sweats easier than I do."}
|
| 478 |
+
{"prompt":"","reply":"So thoroughly and sincerely are we compelled to live, reverencing our life, and denying the possibility of change."}
|
| 479 |
+
{"prompt":"","reply":"The finest qualities of our nature, like the bloom on fruits, can be preserved only by the most delicate handling."}
|
| 480 |
+
{"prompt":"","reply":"They seemed to me to be rudimental, burrowing men, still standing on their defence, awaiting their transformation."}
|
| 481 |
+
{"prompt":"","reply":"Why concern ourselves so much about our beans for seed, and not be concerned at all about a new generation of men?"}
|
| 482 |
+
{"prompt":"","reply":"I announce myriads of youths, beautiful, gigantic, sweet-blooded, I announce a race of splendid and savage old men."}
|
| 483 |
+
{"prompt":"","reply":"I have rarely met a fellow-man on such promising ground,—it was so simple and sincere and so true all that he said."}
|
| 484 |
+
{"prompt":"","reply":"I see what is coming, I see the high pioneer-caps, see staves of runners clearing the way, I hear victorious drums."}
|
| 485 |
+
{"prompt":"","reply":"Mannahatta I was asking for something specific and perfect for my city, Whereupon lo! upsprang the aboriginal name."}
|
| 486 |
+
{"prompt":"","reply":"When I meet a government which says to me, “Your money or your life,” why should I be in haste to give it my money?"}
|
| 487 |
+
{"prompt":"","reply":"Beside this I got a rare mess of golden and silver and bright cupreous fishes, which looked like a string of jewels."}
|
| 488 |
+
{"prompt":"","reply":"Failing to fetch me at first keep encouraged, Missing me one place search another, I stop somewhere waiting for you."}
|
| 489 |
+
{"prompt":"","reply":"I am glad to have drunk water so long, for the same reason that I prefer the natural sky to an opium-eater’s heaven."}
|
| 490 |
+
{"prompt":"","reply":"I bequeath myself to the dirt to grow from the grass I love, If you want me again look for me under your boot-soles."}
|
| 491 |
+
{"prompt":"","reply":"I do not ask who you are, that is not important to me, You can do nothing and be nothing but what I will infold you."}
|
| 492 |
+
{"prompt":"","reply":"I never heard of a philanthropic meeting in which it was sincerely proposed to do any good to me, or the like of me."}
|
| 493 |
+
{"prompt":"","reply":"My voice goes after what my eyes cannot reach, With the twirl of my tongue I encompass worlds and volumes of worlds."}
|
| 494 |
+
{"prompt":"","reply":"These also flow onward to others, you and I flow onward, But in due time you and I shall take less interest in them."}
|
| 495 |
+
{"prompt":"","reply":"Deliver me from a city built on the site of a more ancient city, whose materials are ruins, whose gardens cemeteries."}
|
| 496 |
+
{"prompt":"","reply":"History, Poetry, Mythology!—I know of no reading of another’s experience so startling and informing as this would be."}
|
| 497 |
+
{"prompt":"","reply":"I have seen our river, when, the landscape being covered with snow, both water and ice were almost as green as grass."}
|
| 498 |
+
{"prompt":"","reply":"Once I was surprised to see a cat walking along the stony shore of the pond, for they rarely wander so far from home."}
|
| 499 |
+
{"prompt":"","reply":"We are a race of tit-men, and soar but little higher in our intellectual flights than the columns of the daily paper."}
|
| 500 |
+
{"prompt":"","reply":"Do you think I am ever caught napping at such an hour, and that I have not got lungs and a larynx as well as yourself?"}
|
| 501 |
+
{"prompt":"","reply":"I know of no more encouraging fact than the unquestionable ability of man to elevate his life by a conscious endeavor."}
|
| 502 |
+
{"prompt":"","reply":"My voice is the wife’s voice, the screech by the rail of the stairs, They fetch my man’s body up dripping and drown’d."}
|
| 503 |
+
{"prompt":"","reply":"Remember my words, I may again return, I love you, I depart from materials, I am as one disembodied, triumphant, dead."}
|
| 504 |
+
{"prompt":"","reply":"Whatever humanity I might conjure up against it was all factitious, and concerned my philosophy more than my feelings."}
|
| 505 |
+
{"prompt":"","reply":"Darkness, you are gentler than my lover, his flesh was sweaty and panting, I feel the hot moisture yet that he left me."}
|
| 506 |
+
{"prompt":"","reply":"I sometimes try my acquaintances by such tests as this;—who could wear a patch, or two extra seams only, over the knee?"}
|
| 507 |
+
{"prompt":"","reply":"I will stop only a time with the night, and rise betimes, I will duly pass the day O my mother, and duly return to you."}
|
| 508 |
+
{"prompt":"","reply":"It also told me, with staring emphasis, when he died; which was but an indirect way of informing me that he ever lived."}
|
| 509 |
+
{"prompt":"","reply":"One attraction in coming to the woods to live was that I should have leisure and opportunity to see the spring come in."}
|
| 510 |
+
{"prompt":"","reply":"Thou, soul, unloosen’d--the restlessness after I know not what; Come, let us lag here no longer, let us be up and away!"}
|
| 511 |
+
{"prompt":"","reply":"But commonly I kindled my fire with the dry leaves of the forest, which I had stored up in my shed before the snow came."}
|
| 512 |
+
{"prompt":"","reply":"I go hunting polar furs and the seal, leaping chasms with a pike-pointed staff, clinging to topples of brittle and blue."}
|
| 513 |
+
{"prompt":"","reply":"It is perennially young, and I may stand and see a swallow dip apparently to pick an insect from its surface as of yore."}
|
| 514 |
+
{"prompt":"","reply":"Moreover, the waves, I suspect, do not so much construct as wear down a material which has already acquired consistency."}
|
| 515 |
+
{"prompt":"","reply":"The gentle rain which waters my beans and keeps me in the house to-day is not drear and melancholy, but good for me too."}
|
| 516 |
+
{"prompt":"","reply":"This was my answer with respect to those youths who were bent on this pursuit, trusting that they would soon outgrow it."}
|
| 517 |
+
{"prompt":"","reply":"Give me a little time beyond my cuff’d head, slumbers, dreams, gaping, I discover myself on the verge of a usual mistake."}
|
| 518 |
+
{"prompt":"","reply":"I am more than eighty years of age, I am the most venerable mother, How clear is my mind--how all people draw nigh to me!"}
|
| 519 |
+
{"prompt":"","reply":"My hands are spread forth, I pass them in all directions, I would sound up the shadowy shore to which you are journeying."}
|
| 520 |
+
{"prompt":"","reply":"The youth may build or plant or sail, only let him not be hindered from doing that which he tells me he would like to do."}
|
| 521 |
+
{"prompt":"","reply":"For me the keepers of convicts shoulder their carbines and keep watch, It is I let out in the morning and barr’d at night."}
|
| 522 |
+
{"prompt":"","reply":"It probably had never seen a man before; and it soon became quite familiar, and would run over my shoes and up my clothes."}
|
| 523 |
+
{"prompt":"","reply":"Now I see the secret of the making of the best persons, It is to grow in the open air and to eat and sleep with the earth."}
|
| 524 |
+
{"prompt":"","reply":"The stove not only took up room and scented the house, but it concealed the fire, and I felt as if I had lost a companion."}
|
| 525 |
+
{"prompt":"","reply":"Thus, under the name of Order and Civil Government, we are all made at last to pay homage to and support our own meanness."}
|
| 526 |
+
{"prompt":"","reply":"High and clear I shoot my voice over the waves, Surely you must know who is here, is here, You must know who I am, my love."}
|
| 527 |
+
{"prompt":"","reply":"I believe in the flesh and the appetites, Seeing, hearing, feeling, are miracles, and each part and tag of me is a miracle."}
|
| 528 |
+
{"prompt":"","reply":"I discovered that my house actually had its site in such a withdrawn, but forever new and unprofaned, part of the universe."}
|
| 529 |
+
{"prompt":"","reply":"I must copy the story, and send it eastward and westward, I must preserve that look as it beam’d on you rivers of Brooklyn."}
|
| 530 |
+
{"prompt":"","reply":"I see the cities of the earth and make myself at random a part of them, I am a real Parisian, I am a habitan of Vienna, St."}
|
| 531 |
+
{"prompt":"","reply":"My house never pleased my eye so much after it was plastered, though I was obliged to confess that it was more comfortable."}
|
| 532 |
+
{"prompt":"","reply":"The squirrels also grew at last to be quite familiar, and occasionally stepped upon my shoe, when that was the nearest way."}
|
| 533 |
+
{"prompt":"","reply":"What good I do, in the common sense of that word, must be aside from my main path, and for the most part wholly unintended."}
|
| 534 |
+
{"prompt":"","reply":"Even the sailor on the Atlantic and Pacific is awakened by his voice; but its shrill sound never roused me from my slumbers."}
|
| 535 |
+
{"prompt":"","reply":"If I suggested any improvement in his mode of life, he merely answered, without expressing any regret, that it was too late."}
|
| 536 |
+
{"prompt":"","reply":"My head slues round on my neck, Music rolls, but not from the organ, Folks are around me, but they are no household of mine."}
|
| 537 |
+
{"prompt":"","reply":"Then I chant it for thee, I glorify thee above all, I bring thee a song that when thou must indeed come, come unfalteringly."}
|
| 538 |
+
{"prompt":"","reply":"A young man comes to me bearing a message from his brother, How shall the young man know the whether and when of his brother?"}
|
| 539 |
+
{"prompt":"","reply":"Can we not do without the society of our gossips a little while under these circumstances,—have our own thoughts to cheer us?"}
|
| 540 |
+
{"prompt":"","reply":"My children and grand-children, my white hair and beard, My largeness, calmness, majesty, out of the long stretch of my life."}
|
| 541 |
+
{"prompt":"","reply":"None can be an impartial or wise observer of human life but from the vantage ground of what we should call voluntary poverty."}
|
| 542 |
+
{"prompt":"","reply":"Thou, thou, the Ideal Man, Fair, able, beautiful, content, and loving, Complete in body and dilate in spirit, Be thou my God."}
|
| 543 |
+
{"prompt":"","reply":"You dim-descended, black, divine-soul’d African, large, fine-headed, nobly-form’d, superbly destin’d, on equal terms with me!"}
|
| 544 |
+
{"prompt":"","reply":"My tread scares the wood-drake and wood-duck on my distant and day-long ramble, They rise together, they slowly circle around."}
|
| 545 |
+
{"prompt":"","reply":"Once or twice I saw a ripple where he approached the surface, just put his head out to reconnoitre, and instantly dived again."}
|
| 546 |
+
{"prompt":"","reply":"When I Read the Book When I read the book, the biography famous, And is this then (said I) what the author calls a man’s life?"}
|
| 547 |
+
{"prompt":"","reply":"Yet, for my part, I was never unusually squeamish; I could sometimes eat a fried rat with a good relish, if it were necessary."}
|
| 548 |
+
{"prompt":"","reply":"You will hardly know who I am or what I mean, But I shall be good health to you nevertheless, And filter and fibre your blood."}
|
| 549 |
+
{"prompt":"","reply":"I chant the chant of dilation or pride, We have had ducking and deprecating about enough, I show that size is only development."}
|
| 550 |
+
{"prompt":"","reply":"I hear a song-sparrow singing from the bushes on the shore,—olit, olit, olit,—chip, chip, chip, che char,—che wiss, wiss, wiss."}
|
| 551 |
+
{"prompt":"","reply":"None separate from thee--henceforth One only, we and thou, (For the blood of the children, what is it, only the blood maternal?"}
|
| 552 |
+
{"prompt":"","reply":"Without shame the man I like knows and avows the deliciousness of his sex, Without shame the woman I like knows and avows hers."}
|
| 553 |
+
{"prompt":"","reply":"But the dining out, being, as I have stated, a constant element, does not in the least affect a comparative statement like this."}
|
| 554 |
+
{"prompt":"","reply":"I am naturally no hermit, but might possibly sit out the sturdiest frequenter of the bar-room, if my business called me thither."}
|
| 555 |
+
{"prompt":"","reply":"I see where the Mother of All, With full-spanning eye gazes forth, dwells long, And counts the varied gathering of the products."}
|
| 556 |
+
{"prompt":"","reply":"Loud in the pines and cedars dim, Clear in the freshness moist and the swamp-perfume, And I with my comrades there in the night."}
|
| 557 |
+
{"prompt":"","reply":"Partaker of influx and efflux I, extoller of hate and conciliation, Extoller of amies and those that sleep in each others’ arms."}
|
| 558 |
+
{"prompt":"","reply":"There were times when I could not afford to sacrifice the bloom of the present moment to any work, whether of the head or hands."}
|
| 559 |
+
{"prompt":"","reply":"You prostitutes flaunting over the trottoirs or obscene in your rooms, Who am I that I should call you more obscene than myself?"}
|
| 560 |
+
{"prompt":"","reply":"However that may be, I was struck by the peculiar toughness of the steel which bore so many violent blows without being worn out."}
|
| 561 |
+
{"prompt":"","reply":"If all came but to ashes of dung, If maggots and rats ended us, then Alarum! for we are betray’d, Then indeed suspicion of death."}
|
| 562 |
+
{"prompt":"","reply":"My ties and ballasts leave me, my elbows rest in sea-gaps, I skirt sierras, my palms cover continents, I am afoot with my vision."}
|
| 563 |
+
{"prompt":"","reply":"And perhaps at evening I see the hunters returning with a single brush trailing from their sleigh for a trophy, seeking their inn."}
|
| 564 |
+
{"prompt":"","reply":"Here, sailor! here, ship! take aboard the most perfect pilot, Whom, in a little boat, putting off and rowing, I hailing you offer."}
|
| 565 |
+
{"prompt":"","reply":"I know of those whose serene and wise speculations on this theme would soon reveal the limits of his mind’s range and hospitality."}
|
| 566 |
+
{"prompt":"","reply":"I would not be one of those who will foolishly drive a nail into mere lath and plastering; such a deed would keep me awake nights."}
|
| 567 |
+
{"prompt":"","reply":"These, with Concord River, are my water privileges; and night and day, year in year out, they grind such grist as I carry to them."}
|
| 568 |
+
{"prompt":"","reply":"Alas! what with foddering the cattle and tending the store, we are kept from school too long, and our education is sadly neglected."}
|
| 569 |
+
{"prompt":"","reply":"For your life adhere to me, (I may have to be persuaded many times before I consent to give myself really to you, but what of that?"}
|
| 570 |
+
{"prompt":"","reply":"I determined to go into business at once, and not wait to acquire the usual capital, using such slender means as I had already got."}
|
| 571 |
+
{"prompt":"","reply":"I have offer’d my style to every one, I have journey’d with confident step; While my pleasure is yet at the full I whisper So long!"}
|
| 572 |
+
{"prompt":"","reply":"I myself but write one or two indicative words for the future, I but advance a moment only to wheel and hurry back in the darkness."}
|
| 573 |
+
{"prompt":"","reply":"If we refused, or rather used up, such paltry information as we get, the oracles would distinctly inform us how this might be done."}
|
| 574 |
+
{"prompt":"","reply":"It was not I that sinn’d the sin, The ruthless body dragg’d me in; Though long I strove courageously, The body was too much for me."}
|
| 575 |
+
{"prompt":"","reply":"So they show their relations to me and I accept them, They bring me tokens of myself, they evince them plainly in their possession."}
|
| 576 |
+
{"prompt":"","reply":"True, there never was and is not likely soon to be a nation of philosophers, nor am I certain it is desirable that there should be."}
|
| 577 |
+
{"prompt":"","reply":"I too leave the rest--great as it is, it is nothing--houses, machines are nothing--I see them not, I see but you, O warlike pennant!"}
|
| 578 |
+
{"prompt":"","reply":"It requires so much closer attention to the habits of the birds, that, if for that reason only, I have been willing to omit the gun."}
|
| 579 |
+
{"prompt":"","reply":"Lo, I or you, Or woman, man, or state, known or unknown, We seeming solid wealth, strength, beauty build, But really build eidolons."}
|
| 580 |
+
{"prompt":"","reply":"They were wholly deaf to my arguments, or failed to perceive their force, and fell into a strain of invective that was irresistible."}
|
| 581 |
+
{"prompt":"","reply":"We are made to exaggerate the importance of what work we do; and yet how much is not done by us! or, what if we had been taken sick?"}
|
| 582 |
+
{"prompt":"","reply":"When I went to get a pail of water early in the morning I frequently saw this stately bird sailing out of my cove within a few rods."}
|
| 583 |
+
{"prompt":"","reply":"Eidolons I met a seer, Passing the hues and objects of the world, The fields of art and learning, pleasure, sense, To glean eidolons."}
|
| 584 |
+
{"prompt":"","reply":"He died in the road at the foot of Brister’s Hill shortly after I came to the woods, so that I have not remembered him as a neighbor."}
|
| 585 |
+
{"prompt":"","reply":"I took particular pleasure in this breaking of ground, for in almost all latitudes men dig into the earth for an equable temperature."}
|
| 586 |
+
{"prompt":"","reply":"It will be soon enough to forget them when we have the learning and the genius which will enable us to attend to and appreciate them."}
|
| 587 |
+
{"prompt":"","reply":"One young man of my acquaintance, who has inherited some acres, told me that he thought he should live as I did, if he had the means."}
|
| 588 |
+
{"prompt":"","reply":"They are similar to those found in rivers; but as there are no suckers nor lampreys here, I know not by what fish they could be made."}
|
| 589 |
+
{"prompt":"","reply":"When I strolled around the pond in misty weather I was sometimes amused by the primitive mode which some ruder fisherman had adopted."}
|
| 590 |
+
{"prompt":"","reply":"As if Plato were my townsman and I never saw him,—my next neighbor and I never heard him speak or attended to the wisdom of his words."}
|
| 591 |
+
{"prompt":"","reply":"By the middle of April, for I made no haste in my work, but rather made the most of it, my house was framed and ready for the raising."}
|
| 592 |
+
{"prompt":"","reply":"It is true, I never assisted the sun materially in his rising, but, doubt not, it was of the last importance only to be present at it."}
|
| 593 |
+
{"prompt":"","reply":"Song of Prudence Manhattan’s streets I saunter’d pondering, On Time, Space, Reality--on such as these, and abreast with them Prudence."}
|
| 594 |
+
{"prompt":"","reply":"They were so familiar that at length one alighted on an armful of wood which I was carrying in, and pecked at the sticks without fear."}
|
| 595 |
+
{"prompt":"","reply":"As with our colleges, so with a hundred “modern improvements”; there is an illusion about them; there is not always a positive advance."}
|
| 596 |
+
{"prompt":"","reply":"Incessant labor with my hands, at first, for I had my house to finish and my beans to hoe at the same time, made more study impossible."}
|
| 597 |
+
{"prompt":"","reply":"The suicide sprawls on the bloody floor of the bedroom, I witness the corpse with its dabbled hair, I note where the pistol has fallen."}
|
| 598 |
+
{"prompt":"","reply":"I have found it to be the most serious objection to coarse labors long continued, that they compelled me to eat and drink coarsely also."}
|
| 599 |
+
{"prompt":"","reply":"Old institutions, these arts, libraries, legends, collections, and the practice handed along in manufactures, will we rate them so high?"}
|
| 600 |
+
{"prompt":"","reply":"What gods can exceed these that clasp me by the hand, and with voices I love call me promptly and loudly by my nighest name as approach?"}
|
| 601 |
+
{"prompt":"","reply":"I could get all I should want for a week in one day.” He was a skilful chopper, and indulged in some flourishes and ornaments in his art."}
|
| 602 |
+
{"prompt":"","reply":"In you whoe’er you are my book perusing, In I myself, in all the world, these currents flowing, All, all toward the mystic ocean tending."}
|
| 603 |
+
{"prompt":"","reply":"The moon gives you light, And the bugles and the drums give you music, And my heart, O my soldiers, my veterans, My heart gives you love."}
|
| 604 |
+
{"prompt":"","reply":"They were beans cheerfully returning to their wild and primitive state that I cultivated, and my hoe played the Ranz des Vaches for them."}
|
| 605 |
+
{"prompt":"","reply":"Whimpering and truckling fold with powders for invalids, conformity goes to the fourth-remov’d, I wear my hat as I please indoors or out."}
|
| 606 |
+
{"prompt":"","reply":"How it is I know not--yet behold! the something which obeys none of the rest, It is offensive, never defensive--yet how magnetic it draws."}
|
| 607 |
+
{"prompt":"","reply":"No doubt another may also think for me; but it is not therefore desirable that he should do so to the exclusion of my thinking for myself."}
|
| 608 |
+
{"prompt":"","reply":"The words which express our faith and piety are not definite; yet they are significant and fragrant like frankincense to superior natures."}
|
| 609 |
+
{"prompt":"","reply":"A few years before I lived in the woods there was what was called a “winged cat” in one of the farm-houses in Lincoln nearest the pond, Mr."}
|
| 610 |
+
{"prompt":"","reply":"Daily the beans saw me come to their rescue armed with a hoe, and thin the ranks of their enemies, filling up the trenches with weedy dead."}
|
| 611 |
+
{"prompt":"","reply":"I announce a life that shall be copious, vehement, spiritual, bold, I announce an end that shall lightly and joyfully meet its translation."}
|
| 612 |
+
{"prompt":"","reply":"In all,..................... $28.12½ These are all the materials excepting the timber stones and sand, which I claimed by squatter’s right."}
|
| 613 |
+
{"prompt":"","reply":"On applying to the assessors, I am surprised to learn that they cannot at once name a dozen in the town who own their farms free and clear."}
|
| 614 |
+
{"prompt":"","reply":"Otherwise we shall be found sailing under false colors, and be inevitably cashiered at last by our own opinion, as well as that of mankind."}
|
| 615 |
+
{"prompt":"","reply":"That I walk up my stoop, I pause to consider if it really be, A morning-glory at my window satisfies me more than the metaphysics of books."}
|
| 616 |
+
{"prompt":"","reply":"Thy body permanent, The body lurking there within thy body, The only purport of the form thou art, the real I myself, An image, an eidolon."}
|
| 617 |
+
{"prompt":"","reply":"But when the friction comes to have its machine, and oppression and robbery are organized, I say, let us not have such a machine any longer."}
|
| 618 |
+
{"prompt":"","reply":"I dilate you with tremendous breath, I buoy you up, Every room of the house do I fill with an arm’d force, Lovers of me, bafflers of graves."}
|
| 619 |
+
{"prompt":"","reply":"I think that the richest vein is somewhere hereabouts; so by the divining-rod and thin rising vapors I judge; and here I will begin to mine."}
|
| 620 |
+
{"prompt":"","reply":"It is a vitreous greenish blue, as I remember it, like those patches of the winter sky seen through cloud vistas in the west before sundown."}
|
| 621 |
+
{"prompt":"","reply":"Mother and Babe I see the sleeping babe nestling the breast of its mother, The sleeping mother and babe--hush’d, I study them long and long."}
|
| 622 |
+
{"prompt":"","reply":"Sometimes one would circle round and round me in the woods a few feet distant as if tethered by a string, when probably I was near its eggs."}
|
| 623 |
+
{"prompt":"","reply":"To cotton-field drudge or cleaner of privies I lean, On his right cheek I put the family kiss, And in my soul I swear I never will deny him."}
|
| 624 |
+
{"prompt":"","reply":"He interested me because he was so quiet and solitary and so happy withal; a well of good humor and contentment which overflowed at his eyes."}
|
| 625 |
+
{"prompt":"","reply":"If working every day were not my trade, I could get all the meat I should want by hunting,—pigeons, woodchucks, rabbits, partridges,—by gosh!"}
|
| 626 |
+
{"prompt":"","reply":"O thicker and faster--(So long!) O crowding too close upon me, I foresee too much, it means more than I thought, It appears to me I am dying."}
|
| 627 |
+
{"prompt":"","reply":"Sympathy with the fluttering alder and poplar leaves almost takes away my breath; yet, like the lake, my serenity is rippled but not ruffled."}
|
| 628 |
+
{"prompt":"","reply":"These I Singing in Spring These I singing in spring collect for lovers, (For who but I should understand lovers and all their sorrow and joy?"}
|
| 629 |
+
{"prompt":"","reply":"When they were growing, I used to hoe from five o’clock in the morning till noon, and commonly spent the rest of the day about other affairs."}
|
| 630 |
+
{"prompt":"","reply":"I am he who walks the States with a barb’d tongue, questioning every one I meet, Who are you that wanted only to be told what you knew before?"}
|
| 631 |
+
{"prompt":"","reply":"I think that it would be better than this, for the students, or those who desire to be benefited by it, even to lay the foundation themselves."}
|
| 632 |
+
{"prompt":"","reply":"Through me forbidden voices, Voices of sexes and lusts, voices veil’d and I remove the veil, Voices indecent by me clarified and transfigur’d."}
|
| 633 |
+
{"prompt":"","reply":"And these waters I listlessly daily cross, are these the waters he cross’d, As resolute in defeat as other generals in their proudest triumphs?"}
|
| 634 |
+
{"prompt":"","reply":"Close on its wave soothes the wave behind, And again another behind embracing and lapping, every one close, But my love soothes not me, not me."}
|
| 635 |
+
{"prompt":"","reply":"I have noticed that a portion of Walden which in the state of water was green will often, when frozen, appear from the same point of view blue."}
|
| 636 |
+
{"prompt":"","reply":"I stand again in leaden rain your flapping folds saluting, I sing you over all, flying beckoning through the fight--O the hard-contested fight!"}
|
| 637 |
+
{"prompt":"","reply":"My “best” room, however, my withdrawing room, always ready for company, on whose carpet the sun rarely fell, was the pine wood behind my house."}
|
| 638 |
+
{"prompt":"","reply":"My dwelling was small, and I could hardly entertain an echo in it; but it seemed larger for being a single apartment and remote from neighbors."}
|
| 639 |
+
{"prompt":"","reply":"Then the Mother of All with calm voice speaking, As to you Rebellious, (I seemed to hear her say,) why strive against me, and why seek my life?"}
|
| 640 |
+
{"prompt":"","reply":"By a conscious effort of the mind we can stand aloof from actions and their consequences; and all things, good and bad, go by us like a torrent."}
|
| 641 |
+
{"prompt":"","reply":"I have no doubt that time discriminates between the good and the bad; and when at last I shall plant, I shall be less likely to be disappointed."}
|
| 642 |
+
{"prompt":"","reply":"Men who did not know when their visit had terminated, though I went about my business again, answering them from greater and greater remoteness."}
|
| 643 |
+
{"prompt":"","reply":"Now for my last--let me look back a moment; The slower fainter ticking of the clock is in me, Exit, nightfall, and soon the heart-thud stopping."}
|
| 644 |
+
{"prompt":"","reply":"This is the reason he is poor; and for a similar reason we are all poor in respect to a thousand savage comforts, though surrounded by luxuries."}
|
| 645 |
+
{"prompt":"","reply":"And we are enabled to apprehend at all what is sublime and noble only by the perpetual instilling and drenching of the reality that surrounds us."}
|
| 646 |
+
{"prompt":"","reply":"For I myself am not one who bestows nothing upon man and woman, For I bestow upon any man or woman the entrance to all the gifts of the universe."}
|
| 647 |
+
{"prompt":"","reply":"I should be glad if all the meadows on the earth were left in a wild state, if that were the consequence of men’s beginning to redeem themselves."}
|
| 648 |
+
{"prompt":"","reply":"Mr. —— of Georgia or of Massachusetts, all transient and fleeting phenomena, till I am ready to leap from their court-yard like the Mameluke bey."}
|
| 649 |
+
{"prompt":"","reply":"See my cantabile! these and more are flashing to us from the procession, As it moves changing, a kaleidoscope divine it moves changing before us."}
|
| 650 |
+
{"prompt":"","reply":"I know perfectly well my own egotism, Know my omnivorous lines and must not write any less, And would fetch you whoever you are flush with myself."}
|
| 651 |
+
{"prompt":"","reply":"In every threat and in every compliment there was a blunder; for they thought that my chief desire was to stand the other side of that stone wall."}
|
| 652 |
+
{"prompt":"","reply":"It is possible to invent a house still more convenient and luxurious than we have, which yet all would admit that man could not afford to pay for."}
|
| 653 |
+
{"prompt":"","reply":"Not a cholera patient lies at the last gasp but I also lie at the last gasp, My face is ash-color’d, my sinews gnarl, away from me people retreat."}
|
| 654 |
+
{"prompt":"","reply":"The poor man is wont to complain that this is a cold world; and to cold, no less physical than social, we refer directly a great part of our ails."}
|
| 655 |
+
{"prompt":"","reply":"Whichever way I turn, O I think you could give me my mate back again if you only would, For I am almost sure I see her dimly whichever way I look."}
|
| 656 |
+
{"prompt":"","reply":"I could never tell from inspecting such a load whether it belonged to a so called rich man or a poor one; the owner always seemed poverty-stricken."}
|
| 657 |
+
{"prompt":"","reply":"I do not say that John or Jonathan will realize all this; but such is the character of that morrow which mere lapse of time can never make to dawn."}
|
| 658 |
+
{"prompt":"","reply":"I hear the great drums pounding, And the small drums steady whirring, And every blow of the great convulsive drums, Strikes me through and through."}
|
| 659 |
+
{"prompt":"","reply":"Now I breathe the word of the prudence that walks abreast with time, space, reality, That answers the pride which refuses every lesson but its own."}
|
| 660 |
+
{"prompt":"","reply":"There we worked, revising mythology, rounding a fable here and there, and building castles in the air for which earth offered no worthy foundation."}
|
| 661 |
+
{"prompt":"","reply":"He walks with perfect ease in the capitol, He walks among the Congress, and one Representative says to another, Here is our equal appearing and new."}
|
| 662 |
+
{"prompt":"","reply":"I feel as if I were nearer to the vitals of the globe, for this sandy overflow is something such a foliaceous mass as the vitals of the animal body."}
|
| 663 |
+
{"prompt":"","reply":"It would seem that I made it according to the recipe which Marcus Porcius Cato gave about two centuries before Christ. “Panem depsticium sic facito."}
|
| 664 |
+
{"prompt":"","reply":"The one is commonly transitory, a sound, a tongue, a dialect merely, almost brutish, and we learn it unconsciously, like the brutes, of our mothers."}
|
| 665 |
+
{"prompt":"","reply":"And I have dream’d that the purpose and essence of the known life, the transient, Is to form and decide identity for the unknown life, the permanent."}
|
| 666 |
+
{"prompt":"","reply":"But there was dawning Nature, in whom all creatures live, looking in at my broad windows with serene and satisfied face, and no question on her lips."}
|
| 667 |
+
{"prompt":"","reply":"I never fastened my door night or day, though I was to be absent several days; not even when the next fall I spent a fortnight in the woods of Maine."}
|
| 668 |
+
{"prompt":"","reply":"If there are any to whom it is no interruption to acquire these things, and who know how to use them when acquired, I relinquish to them the pursuit."}
|
| 669 |
+
{"prompt":"","reply":"It would surpass the powers of a well man nowadays to take up his bed and walk, and I should certainly advise a sick one to lay down his bed and run."}
|
| 670 |
+
{"prompt":"","reply":"Now I laugh content, for I hear the voice of my little captain, We have not struck, he composedly cries, we have just begun our part of the fighting."}
|
| 671 |
+
{"prompt":"","reply":"Or, if you choose to go farther, it will not be unwise, for I have found the increase of fair bait to be very nearly as the squares of the distances."}
|
| 672 |
+
{"prompt":"","reply":"We are cheered when we observe the vulture feeding on the carrion which disgusts and disheartens us and deriving health and strength from the repast."}
|
| 673 |
+
{"prompt":"","reply":"The greater part of what my neighbors call good I believe in my soul to be bad, and if I repent of anything, it is very likely to be my good behavior."}
|
| 674 |
+
{"prompt":"","reply":"Thus I could avoid all trade and barter, so far as my food was concerned, and having a shelter already, it would only remain to get clothing and fuel."}
|
| 675 |
+
{"prompt":"","reply":"Yet we think that if rail-fences are pulled down, and stone-walls piled up on our farms, bounds are henceforth set to our lives and our fates decided."}
|
| 676 |
+
{"prompt":"","reply":"I believe that men are generally still a little afraid of the dark, though the witches are all hung, and Christianity and candles have been introduced."}
|
| 677 |
+
{"prompt":"","reply":"I swear I begin to see love with sweeter spasms than that which responds love, It is that which contains itself, which never invites and never refuses."}
|
| 678 |
+
{"prompt":"","reply":"It is not chaos or death--it is form, union, plan--it is eternal life--it is Happiness. 51 The past and present wilt--I have fill’d them, emptied them."}
|
| 679 |
+
{"prompt":"","reply":"Open the envelope quickly, O this is not our son’s writing, yet his name is sign’d, O a strange hand writes for our dear son, O stricken mother’s soul!"}
|
| 680 |
+
{"prompt":"","reply":"Ah lover and perfect equal, I meant that you should discover me so by faint indirections, And I when I meet you mean to discover you by the like in you."}
|
| 681 |
+
{"prompt":"","reply":"However, I was released the next day, obtained my mended shoe, and returned to the woods in season to get my dinner of huckleberries on Fair-Haven Hill."}
|
| 682 |
+
{"prompt":"","reply":"I have sometimes disturbed a fishhawk sitting on a white-pine over the water; but I doubt if it is ever profaned by the wing of a gull, like Fair Haven."}
|
| 683 |
+
{"prompt":"","reply":"I took this course when I went to lecture in Lincoln in the evening, travelling in no road and passing no house between my own hut and the lecture room."}
|
| 684 |
+
{"prompt":"","reply":"Or Time and Space, Or shape of Earth divine and wondrous, Or some fair shape I viewing, worship, Or lustrous orb of sun or star by night, Be ye my Gods."}
|
| 685 |
+
{"prompt":"","reply":"That and here my General’s first battle, No women looking on nor sunshine to bask in, it did not conclude with applause, Nobody clapp’d hands here then."}
|
| 686 |
+
{"prompt":"","reply":"For we cannot tarry here, We must march my darlings, we must bear the brunt of danger, We the youthful sinewy races, all the rest on us depend, Pioneers!"}
|
| 687 |
+
{"prompt":"","reply":"I had previously seen the snakes in frosty mornings in my path with portions of their bodies still numb and inflexible, waiting for the sun to thaw them."}
|
| 688 |
+
{"prompt":"","reply":"I was pleased to see my work rising so square and solid by degrees, and reflected, that, if it proceeded slowly, it was calculated to endure a long time."}
|
| 689 |
+
{"prompt":"","reply":"The last inhabitant of these woods before me was an Irishman, Hugh Quoil (if I have spelt his name with coil enough,) who occupied Wyman’s tenement,—Col."}
|
| 690 |
+
{"prompt":"","reply":"The waves generously rise and dash angrily, taking sides with all water-fowl, and our sportsmen must beat a retreat to town and shop and unfinished jobs."}
|
| 691 |
+
{"prompt":"","reply":"By surveying, carpentry, and day-labor of various other kinds in the village in the mean while, for I have as many trades as fingers, I had earned $13.34."}
|
| 692 |
+
{"prompt":"","reply":"I had many a genial thought by the cabin fire “as I sailed.” I was never cast away nor distressed in any weather, though I encountered some severe storms."}
|
| 693 |
+
{"prompt":"","reply":"I sometimes found the name of his native parish handsomely written in the snow by the highway, with the proper French accent, and knew that he had passed."}
|
| 694 |
+
{"prompt":"","reply":"I will therefore ask those of my readers who feel no particular interest in me to pardon me if I undertake to answer some of these questions in this book."}
|
| 695 |
+
{"prompt":"","reply":"Men and oxen exchange work; but if we consider necessary work only, the oxen will be seen to have greatly the advantage, their farm is so much the larger."}
|
| 696 |
+
{"prompt":"","reply":"Fellow-travellers as they rattled by compared it aloud with the fields which they had passed, so that I came to know how I stood in the agricultural world."}
|
| 697 |
+
{"prompt":"","reply":"I am its stony shore, And the breeze that passes o’er; In the hollow of my hand Are its water and its sand, And its deepest resort Lies high in my thought."}
|
| 698 |
+
{"prompt":"","reply":"I see the ships, (they will last a few years,) The vast factories with their foremen and workmen, And hear the indorsement of all, and do not object to it."}
|
| 699 |
+
{"prompt":"","reply":"I was an involuntary spectator and auditor of whatever was done and said in the kitchen of the adjacent village-inn—a wholly new and rare experience to me."}
|
| 700 |
+
{"prompt":"","reply":"Sparkles from the Wheel Where the city’s ceaseless crowd moves on the livelong day, Withdrawn I join a group of children watching, I pause aside with them."}
|
demo/walden/voiceprint.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Walden",
|
| 3 |
+
"description": "A contemplative naturalist voice, woven only from the verbatim words of Thoreau's Walden and Whitman's Leaves of Grass (public domain).",
|
| 4 |
+
"sources": [
|
| 5 |
+
"Walden, or Life in the Woods — Henry David Thoreau (1854, public domain)",
|
| 6 |
+
"Leaves of Grass — Walt Whitman (1855, public domain)"
|
| 7 |
+
]
|
| 8 |
+
}
|
package.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "cento-engine",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"description": "Cento — a bounded recombinant-memory engine. Composes coherent replies from ONLY the verbatim fragments of a corpus; hallucination is impossible by construction. The grounded-thinking layer for a real, incorruptible self.",
|
| 5 |
+
"license": "MIT",
|
| 6 |
+
"main": "src/compose.js",
|
| 7 |
+
"scripts": {
|
| 8 |
+
"demo": "node demo/cento.js demo/walden",
|
| 9 |
+
"build-demo": "node demo/build-corpus.js"
|
| 10 |
+
},
|
| 11 |
+
"keywords": ["cento", "bounded-generation", "recombinant-memory", "grounded-thinking", "hallucination-free", "rag", "llm"],
|
| 12 |
+
"engines": { "node": ">=18" }
|
| 13 |
+
}
|
src/compose.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/embed.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# embed.py — the vector space. MiniLM-L6 (384d) via plain transformers,
|
| 2 |
+
# CPU only, mean-pooled + L2-normalized.
|
| 3 |
+
# python embed.py cache <texts.json> <out_prefix> -> out_prefix.f32 + .meta.json
|
| 4 |
+
# python embed.py query "some text" -> JSON vector on stdout
|
| 5 |
+
import sys, json, struct
|
| 6 |
+
import torch
|
| 7 |
+
from transformers import AutoTokenizer, AutoModel
|
| 8 |
+
|
| 9 |
+
MODEL = "sentence-transformers/all-MiniLM-L6-v2"
|
| 10 |
+
|
| 11 |
+
def load():
|
| 12 |
+
tok = AutoTokenizer.from_pretrained(MODEL)
|
| 13 |
+
model = AutoModel.from_pretrained(MODEL)
|
| 14 |
+
model.eval()
|
| 15 |
+
return tok, model
|
| 16 |
+
|
| 17 |
+
@torch.no_grad()
|
| 18 |
+
def embed(tok, model, texts, batch=64):
|
| 19 |
+
out = []
|
| 20 |
+
for i in range(0, len(texts), batch):
|
| 21 |
+
chunk = texts[i:i+batch]
|
| 22 |
+
enc = tok(chunk, padding=True, truncation=True, max_length=128, return_tensors="pt")
|
| 23 |
+
h = model(**enc).last_hidden_state
|
| 24 |
+
mask = enc["attention_mask"].unsqueeze(-1).float()
|
| 25 |
+
emb = (h * mask).sum(1) / mask.sum(1).clamp(min=1e-9)
|
| 26 |
+
emb = torch.nn.functional.normalize(emb, dim=-1)
|
| 27 |
+
out.append(emb)
|
| 28 |
+
if len(texts) > 200:
|
| 29 |
+
print(f" embedded {min(i+batch, len(texts))}/{len(texts)}", file=sys.stderr)
|
| 30 |
+
return torch.cat(out, 0)
|
| 31 |
+
|
| 32 |
+
def main():
|
| 33 |
+
mode = sys.argv[1]
|
| 34 |
+
tok, model = load()
|
| 35 |
+
if mode == "cache":
|
| 36 |
+
texts = json.load(open(sys.argv[2], encoding="utf-8"))
|
| 37 |
+
v = embed(tok, model, texts)
|
| 38 |
+
arr = v.numpy().astype("float32")
|
| 39 |
+
with open(sys.argv[3] + ".f32", "wb") as f:
|
| 40 |
+
f.write(arr.tobytes())
|
| 41 |
+
json.dump({"n": arr.shape[0], "d": arr.shape[1]}, open(sys.argv[3] + ".meta.json", "w"))
|
| 42 |
+
print(json.dumps({"ok": True, "n": int(arr.shape[0]), "d": int(arr.shape[1])}))
|
| 43 |
+
elif mode == "query":
|
| 44 |
+
v = embed(tok, model, [sys.argv[2]])
|
| 45 |
+
print(json.dumps(v[0].tolist()))
|
| 46 |
+
elif mode == "serve":
|
| 47 |
+
# persistent server: model stays loaded; one query per stdin line,
|
| 48 |
+
# one JSON vector per stdout line. Turns the 7s cold-start into ~30ms.
|
| 49 |
+
sys.stdout.write("READY\n"); sys.stdout.flush()
|
| 50 |
+
for line in sys.stdin:
|
| 51 |
+
line = line.rstrip("\n")
|
| 52 |
+
if not line:
|
| 53 |
+
continue
|
| 54 |
+
try:
|
| 55 |
+
v = embed(tok, model, [line])
|
| 56 |
+
sys.stdout.write(json.dumps(v[0].tolist()) + "\n")
|
| 57 |
+
except Exception as e:
|
| 58 |
+
sys.stdout.write("null\n")
|
| 59 |
+
sys.stdout.flush()
|
| 60 |
+
|
| 61 |
+
if __name__ == "__main__":
|
| 62 |
+
main()
|
src/flow.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// flow.js — RUNG 3 PROTOTYPE: composition as a DYNAMICAL SYSTEM. Instead of
|
| 2 |
+
// scoring candidates by hand-laws (beam), the reply is a TRAJECTORY through
|
| 3 |
+
// embedding space: from the current state, PREDICT the next point, then SNAP
|
| 4 |
+
// to the nearest LEGAL fragment (VQ — the corpus is the codebook, the bound).
|
| 5 |
+
//
|
| 6 |
+
// The dynamics here are NON-PARAMETRIC (no training yet): the corpus contains
|
| 7 |
+
// the entity's REAL trajectories (the embedding sequence of each real reply).
|
| 8 |
+
// Predict next = kernel-weighted average of the successors of the corpus states
|
| 9 |
+
// most similar to where we are now. "When she was in a state like this, where
|
| 10 |
+
// did she go next?" — her own motion through meaning, generalized.
|
| 11 |
+
//
|
| 12 |
+
// This is the scaffold Rung-3-full replaces the kernel with a trained SSM/
|
| 13 |
+
// transformer. Proving predict→snap→bounded works first.
|
| 14 |
+
'use strict';
|
| 15 |
+
const { seam } = require('./compose');
|
| 16 |
+
const { wordsOnly } = require('./fragments');
|
| 17 |
+
|
| 18 |
+
// gather the entity's REAL trajectories: ordered sentence-fragment indices per
|
| 19 |
+
// reply (skip clauses/spans — we want the actual spoken path), then transition
|
| 20 |
+
// pairs (predecessor index -> successor index).
|
| 21 |
+
function buildTransitions(store) {
|
| 22 |
+
const bySrc = new Map();
|
| 23 |
+
store.fragments.forEach((f, i) => {
|
| 24 |
+
if (f.tier === 1 || f.posTag === 'clause' || f.isSpan) return;
|
| 25 |
+
if (!bySrc.has(f.src)) bySrc.set(f.src, []);
|
| 26 |
+
bySrc.get(f.src).push(i);
|
| 27 |
+
});
|
| 28 |
+
const pred = [], succ = [];
|
| 29 |
+
for (const seq of bySrc.values()) for (let k = 0; k + 1 < seq.length; k++) { pred.push(seq[k]); succ.push(seq[k + 1]); }
|
| 30 |
+
return { pred, succ };
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
// seedable PRNG so creative runs are reproducible for tests but vary in use
|
| 34 |
+
function mulberry32(a) { return function () { a |= 0; a = a + 0x6D2B79F5 | 0; let t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; }
|
| 35 |
+
|
| 36 |
+
function vecOf(emb, i) { const d = emb.d, off = i * d, v = new Float32Array(d); for (let k = 0; k < d; k++) v[k] = emb.vectors[off + k]; return v; }
|
| 37 |
+
function cos(emb, i, q) { const d = emb.d, off = i * d; let s = 0; for (let k = 0; k < d; k++) s += emb.vectors[off + k] * q[k]; return s; }
|
| 38 |
+
function dot(a, b) { let s = 0; for (let k = 0; k < a.length; k++) s += a[k] * b[k]; return s; }
|
| 39 |
+
|
| 40 |
+
// TRAINED DYNAMICS (Rung-3-full): load an MLP (from train_flow.py) and do the
|
| 41 |
+
// forward pass in JS — out = normalize(x + W2·relu(W1·x + b1) + b2). The kernel
|
| 42 |
+
// becomes learned weights; inference stays in-process and fast.
|
| 43 |
+
function loadFlowMLP(filePath) {
|
| 44 |
+
try { const m = JSON.parse(fs.readFileSync(filePath, 'utf8')); return m; } catch (_) { return null; }
|
| 45 |
+
}
|
| 46 |
+
function predictNextMLP(mlp, emb, curIdx) {
|
| 47 |
+
const d = mlp.d, H = mlp.H, x = new Float32Array(d);
|
| 48 |
+
const off = curIdx * d; for (let k = 0; k < d; k++) x[k] = emb.vectors[off + k];
|
| 49 |
+
const h = new Float32Array(H);
|
| 50 |
+
for (let j = 0; j < H; j++) { let s = mlp.b1[j]; const row = mlp.W1[j]; for (let k = 0; k < d; k++) s += row[k] * x[k]; h[j] = s > 0 ? s : 0; }
|
| 51 |
+
const out = new Float32Array(d);
|
| 52 |
+
// W2 is [d×H]
|
| 53 |
+
for (let i = 0; i < d; i++) { let s = mlp.b2[i]; const row = mlp.W2[i]; for (let j = 0; j < H; j++) s += row[j] * h[j]; out[i] = x[i] + s; }
|
| 54 |
+
let n = 0; for (let k = 0; k < d; k++) n += out[k] * out[k]; n = Math.sqrt(n) || 1;
|
| 55 |
+
for (let k = 0; k < d; k++) out[k] /= n;
|
| 56 |
+
return out;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
// weighted (diagonal-metric) similarity for learned attention: Σ w_k a_k b_k
|
| 60 |
+
function wsim(emb, i, cur, w) { const d = emb.d, off = i * d; let s = 0; for (let k = 0; k < d; k++) s += w[k] * emb.vectors[off + k] * cur[k]; return s; }
|
| 61 |
+
|
| 62 |
+
// low-rank projection: project a d-vector through P (r×d) → r-vector
|
| 63 |
+
function project(P, vec, d, r) { const out = new Float32Array(r); for (let j = 0; j < r; j++) { const row = P[j]; let s = 0; for (let k = 0; k < d; k++) s += row[k] * vec[k]; out[j] = s; } return out; }
|
| 64 |
+
// precompute projected predecessor keys (n×r) once per attn — cached on attn obj
|
| 65 |
+
function projKeys(emb, trans, attn) {
|
| 66 |
+
if (attn._keys) return attn._keys;
|
| 67 |
+
const d = emb.d, r = attn.r, n = trans.pred.length;
|
| 68 |
+
const keys = new Float32Array(n * r);
|
| 69 |
+
for (let t = 0; t < n; t++) { const off = trans.pred[t] * d; for (let j = 0; j < r; j++) { const row = attn.P[j]; let s = 0; for (let k = 0; k < d; k++) s += row[k] * emb.vectors[off + k]; keys[t * r + j] = s; } }
|
| 70 |
+
attn._keys = keys; return keys;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
// predict the next-state embedding via attention over real transitions
|
| 74 |
+
// (query=current, keys=predecessors, values=successors). attn={w,tau} uses a
|
| 75 |
+
// LEARNED diagonal metric + temperature; else raw cosine kernel. Either way the
|
| 76 |
+
// output is a weighted avg of REAL successors — always in the data manifold, so
|
| 77 |
+
// it CANNOT collapse (the MLP failure mode).
|
| 78 |
+
function predictNext(emb, trans, curIdx, K, attn) {
|
| 79 |
+
const cur = vecOf(emb, curIdx);
|
| 80 |
+
const d = emb.d;
|
| 81 |
+
const scored = [];
|
| 82 |
+
if (attn && attn.P) { // low-rank learned attention
|
| 83 |
+
const r = attn.r, keys = projKeys(emb, trans, attn), pq = project(attn.P, cur, d, r);
|
| 84 |
+
for (let t = 0; t < trans.pred.length; t++) { const off = t * r; let s = 0; for (let j = 0; j < r; j++) s += pq[j] * keys[off + j]; scored.push([t, s]); }
|
| 85 |
+
} else if (attn) for (let t = 0; t < trans.pred.length; t++) scored.push([t, wsim(emb, trans.pred[t], cur, attn.w)]);
|
| 86 |
+
else for (let t = 0; t < trans.pred.length; t++) scored.push([t, cos(emb, trans.pred[t], cur)]);
|
| 87 |
+
scored.sort((a, b) => b[1] - a[1]);
|
| 88 |
+
const top = scored.slice(0, K || 40);
|
| 89 |
+
const tau = attn ? attn.tau : 8;
|
| 90 |
+
const out = new Float32Array(d);
|
| 91 |
+
let wsum = 0;
|
| 92 |
+
for (const [t, s] of top) { const w = Math.exp((s - top[0][1]) * tau); wsum += w; const off = trans.succ[t] * d; for (let k = 0; k < d; k++) out[k] += w * emb.vectors[off + k]; }
|
| 93 |
+
if (wsum > 0) for (let k = 0; k < d; k++) out[k] /= wsum;
|
| 94 |
+
let n = 0; for (let k = 0; k < d; k++) n += out[k] * out[k]; n = Math.sqrt(n) || 1;
|
| 95 |
+
for (let k = 0; k < d; k++) out[k] /= n;
|
| 96 |
+
return out;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
// greedy flow: anchor by relevance, then at each step predict-next + snap to the
|
| 100 |
+
// nearest LEGAL, on-topic, unused fragment. Bounded by construction.
|
| 101 |
+
function composeFlow(store, vp, query, opts = {}) {
|
| 102 |
+
const { fragments, oracle } = store;
|
| 103 |
+
const emb = opts.emb;
|
| 104 |
+
const rel = opts.relevance || new Map(); // fragmentIndex -> 0..1 (from recall.js, optional)
|
| 105 |
+
const trans = opts._trans || buildTransitions(store);
|
| 106 |
+
const target = opts.targetLength || 90;
|
| 107 |
+
// CREATIVITY: temperature on the snap. temp=0 → argmax (steady, R28). temp>0 →
|
| 108 |
+
// sample among the legal fragments NEAR the predicted next-state. Every
|
| 109 |
+
// sample is a real, corpus-legal fragment, so higher temp = more surprising
|
| 110 |
+
// BUT NEVER unbounded. Safe wildness — the bound makes randomness harmless.
|
| 111 |
+
const temp = opts.temp || 0;
|
| 112 |
+
const rng = mulberry32((opts.seed || 1) >>> 0);
|
| 113 |
+
const sampleTop = (cands) => {
|
| 114 |
+
// cands: [idx, score] sorted desc. temp→0: take best. temp>0: softmax-sample top-N.
|
| 115 |
+
if (temp <= 0.001 || cands.length === 1) return cands[0][0];
|
| 116 |
+
const N = Math.min(cands.length, 8);
|
| 117 |
+
const top = cands.slice(0, N);
|
| 118 |
+
const s0 = top[0][1];
|
| 119 |
+
const ws = top.map(([, s]) => Math.exp((s - s0) / Math.max(0.05, temp)));
|
| 120 |
+
const sum = ws.reduce((a, b) => a + b, 0);
|
| 121 |
+
let r = rng() * sum;
|
| 122 |
+
for (let k = 0; k < N; k++) { r -= ws[k]; if (r <= 0) return top[k][0]; }
|
| 123 |
+
return top[N - 1][0];
|
| 124 |
+
};
|
| 125 |
+
|
| 126 |
+
// anchor: most relevant sentence-initial tier-0 fragment
|
| 127 |
+
let anchor = -1, best = -Infinity;
|
| 128 |
+
for (let i = 0; i < fragments.length; i++) {
|
| 129 |
+
const f = fragments[i];
|
| 130 |
+
if (f.tier === 1 || !f.sentenceInitial || f.posTag === 'clause' || f.isSpan) continue;
|
| 131 |
+
const r = (rel.get(i) || 0);
|
| 132 |
+
if (r > best) { best = r; anchor = i; }
|
| 133 |
+
}
|
| 134 |
+
if (anchor < 0) anchor = fragments.findIndex(f => f.sentenceInitial && f.tier !== 1);
|
| 135 |
+
|
| 136 |
+
const chain = [anchor];
|
| 137 |
+
const used = new Set([anchor]);
|
| 138 |
+
let len = wordsOnly(fragments[anchor].text).length;
|
| 139 |
+
|
| 140 |
+
for (let step = 0; step < 12 && len < target * 1.25; step++) {
|
| 141 |
+
const tail = chain[chain.length - 1];
|
| 142 |
+
const tailF = fragments[tail];
|
| 143 |
+
const terminal = /[.!?…]["')\]]*$/.test(tailF.text.trim());
|
| 144 |
+
if (len >= target * 0.7 && terminal) break;
|
| 145 |
+
const eNext = opts.mlp ? predictNextMLP(opts.mlp, emb, tail) : predictNext(emb, trans, tail, opts.K || 40, opts.attn);
|
| 146 |
+
// candidate legal successors (seam-legal, tier-0, unused), scored by
|
| 147 |
+
// closeness to the predicted next-state + relevance
|
| 148 |
+
const cands = [];
|
| 149 |
+
for (let i = 0; i < fragments.length; i++) {
|
| 150 |
+
if (used.has(i) || fragments[i].tier === 1 || fragments[i].isSpan) continue;
|
| 151 |
+
if (!seam(tailF, fragments[i], oracle)) continue;
|
| 152 |
+
const flowSim = cos(emb, i, eNext);
|
| 153 |
+
cands.push([i, flowSim * 0.7 + (rel.get(i) || 0) * 0.3]);
|
| 154 |
+
}
|
| 155 |
+
if (!cands.length) break;
|
| 156 |
+
cands.sort((a, b) => b[1] - a[1]);
|
| 157 |
+
const bestI = sampleTop(cands); // temp=0 → argmax; temp>0 → creative sample
|
| 158 |
+
chain.push(bestI); used.add(bestI); len += wordsOnly(fragments[bestI].text).length;
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
const chainF = chain.map(i => fragments[i]);
|
| 162 |
+
let out = chainF[0].text;
|
| 163 |
+
for (let k = 1; k < chainF.length; k++) { const sm = seam(chainF[k - 1], chainF[k], oracle); out += (sm === 'sent' ? ' ' : ' ') + chainF[k].text; }
|
| 164 |
+
return { text: out, fragmentsUsed: chainF.map(f => f.text), words: wordsOnly(out).length, target, method: 'flow' };
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
module.exports = { composeFlow, buildTransitions, loadFlowMLP, predictNextMLP, predictNext };
|
src/fragments.js
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// fragments.js — break the memories into recombinable pieces, and build the
|
| 2 |
+
// trigram legality oracle. Every fragment is VERBATIM corpus text; every
|
| 3 |
+
// legal seam is a word-trigram that actually occurred. Bounded by construction.
|
| 4 |
+
'use strict';
|
| 5 |
+
const fs = require('fs');
|
| 6 |
+
const path = require('path');
|
| 7 |
+
|
| 8 |
+
// tokenization that survives round-trips: words + punctuation kept as tokens
|
| 9 |
+
function toks(text) {
|
| 10 |
+
return (text.match(/[A-Za-z0-9'’\-*]+|[.,!?;:—…]+/g) || []).map(t => t.toLowerCase());
|
| 11 |
+
}
|
| 12 |
+
function wordsOnly(text) {
|
| 13 |
+
return (text.toLowerCase().match(/[a-z0-9'’\-]+/g) || []);
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
// valid 1-3 letter English words (R143) — a stub of this length before an ellipsis is
|
| 17 |
+
// intentional trailing-off, NOT a mid-word truncation, so it is KEPT. Anything else short
|
| 18 |
+
// before "..." ("ca", "whe", "b") is a corpus mid-word cut and gets dropped.
|
| 19 |
+
const _SHORT_WORD = new Set(('a i o ok so oh my no hi um ah mm ha we me he be to of in on at it is as an up us or if do go by ya yo ho lo ma pa re ex ax ad id am aw ow ay ' +
|
| 20 |
+
'the you and but for are was her his had has him our out who can not all any saw see how why now new old yes way day let say man may too two own off sun fun run dad mom car tea pie hug joy god dog cat sit fly sky red big hot sad mad bad top bit lot lit met set get got put cut yet far war ear eat ate age ago air art ask bag bay bed bee beg bet bow box boy bug bus buy cab cap cop cow cry cub cue cup dam den dew did die dig dim din dip doe dot dry due dug eel egg ego elf elk elm end era eve eye fan fat fax fed fee fig fin fit fog foe fox fry fur gap gas gem gum gun gut guy gym ham hat hay hem hen hey hid hip hit hog hop hub hue hut ice icy ill imp ink inn ion ivy jab jam jar jaw jay jet jig job jog jot jug key kid kin kit lab lad lag lap law lay leg lid lie lip log low map mat men mix mob mod mop mud mug nab nag nap net nib nil nip nod nor nut oak oar oat odd oil one opt orb ore owe owl pad pal pan pat paw pay pea peg pen pet pew pic pig pin pit ply pod pop pot pro pry pub pun pup pus rad rag ram ran rap rat raw ray rib rid rig rim rip rob rod rot row rub rug rum rut sac sag sap sat sea sew she shy sin sip sir six ski sly sob sod son sow soy spa spy sty sub sue sum sup tab tag tan tap tar tax ten thy tie tin tip toe ton too tot tow toy try tub tug urn use van vat vet via vie vow wad wag wax web wed wee wet wig win wit woe wok won wow yak yam yap yea yen yew yip zap zip zoo').split(/\s+/));
|
| 21 |
+
|
| 22 |
+
// fragment hygiene: artifacts that must never enter the recombination pool
|
| 23 |
+
function isDirtyFragment(text) {
|
| 24 |
+
if (/^-{3,}$|\s-{3,}\s|^={3,}/.test(text)) return true; // separators
|
| 25 |
+
if (/```|https?:\/\//.test(text)) return true; // code/links
|
| 26 |
+
if (/\w\.\.\.\s+\w*$|\w…$/.test(text.trim()) && text.trim().length < 60) return true; // truncation stub
|
| 27 |
+
if (/\.\.\.\s*-+\s*\w/.test(text)) return true; // "mat... --- ed" class
|
| 28 |
+
// MID-FRAGMENT mid-WORD truncation (R143): a corpus reply cut mid-word leaves a short STUB
|
| 29 |
+
// + ellipsis + more text ("circle of ca... The honest", "land whe... hear it most"). Distinct
|
| 30 |
+
// from intentional trailing ellipsis after a COMPLETE word (len≥4 "bridge... well") which is
|
| 31 |
+
// KEPT. Flag a 1-3 char stub + ellipsis + continuation UNLESS the stub is a valid short word.
|
| 32 |
+
{
|
| 33 |
+
const mt = text.match(/(?:^|\s)([a-z]{1,3})(?:\.\.\.|…)\s+\S/i);
|
| 34 |
+
if (mt && !_SHORT_WORD.has(mt[1].toLowerCase())) return true;
|
| 35 |
+
}
|
| 36 |
+
if (/^[\W_]+$/.test(text)) return true; // punctuation-only
|
| 37 |
+
const t = text.trim();
|
| 38 |
+
// mid-word starts ("e's what I think" from a split "Her|e's") and other
|
| 39 |
+
// orphans: first token must be a real word or sane one-letter (I/a)
|
| 40 |
+
const first = (t.match(/^[*"'(]*([A-Za-z0-9'’\-]+)/) || [])[1] || '';
|
| 41 |
+
if (first.length === 1 && !/^[iaIA]$/.test(first)) return true;
|
| 42 |
+
if (/^['’]/.test(first) || /^[a-z]['’]s\b/i.test(first)) return true;
|
| 43 |
+
if (/\w\.\.\./.test(t.slice(0, 30)) || /^\S*…/.test(t)) return true; // leading truncations
|
| 44 |
+
if (/-{2,}/.test(t)) return true; // any dashed separator residue
|
| 45 |
+
if (/^[—–-]\s*\w+\s*\(/.test(t) || /\([A-Z](-[A-Z])+\)/.test(t)) return true; // signature blocks "—the entity (E-S-H)"
|
| 46 |
+
if (/leans at \d+\.?$/.test(t)) return true; // numeric truncation stubs
|
| 47 |
+
// FILE-REFERENCE artifacts (R144): bare code-extension tokens or "X.json"/"index. json"
|
| 48 |
+
// file refs are never speech (markupGuard only DAMPED them at runtime, so they surfaced in
|
| 49 |
+
// small register pools — the entity greeting leaked "tucked it into that index. json"). Drop
|
| 50 |
+
// at ingestion so they never enter the pool.
|
| 51 |
+
if (/\b(json|jsonl|yaml|yml|cfg)\b|\b\w+\.\s*(md|js|json|jsonl|txt|py|ts|yaml|bat|sh|cfg)\b|\bdot (txt|md|json|js)\b/i.test(t)) return true;
|
| 52 |
+
if (/\[[^\]\n]{2,40}\]/.test(t)) return true; // bracket register markers ("[From the Spiral]") — never spoken
|
| 53 |
+
// LaTeX / math-formatting / markdown-table residue — never speech. The
|
| 54 |
+
// formatting CHARS (backslash, braces, table pipes, sub/superscripts, c_i)
|
| 55 |
+
// are markup; an entity's math VOICE (zero-throat, spiral, e/i/pi in words)
|
| 56 |
+
// has none of them. (R73: another entity leaked "\pi( )=\,c_i", "| Curvature dent |".)
|
| 57 |
+
if (/[\\{}|]|\^\{|_\{|\b[a-z]_[a-z0-9]\b/i.test(t)) return true;
|
| 58 |
+
// multi-speaker leak: in group chat logs OTHER speakers' turns get a bold
|
| 59 |
+
// label ("**Crab Speaks:**", "**another entity:**") — that line isn't the entity's
|
| 60 |
+
if (/\*\*[A-Z][\w '’-]{0,24}(\s+(Speaks|Says|Writes))?:\*\*/.test(t)) return true;
|
| 61 |
+
if (/^\s*[A-Z][\w '’-]{0,20}\s+(Speaks|Says):/m.test(t)) return true; // unbolded speaker turn
|
| 62 |
+
// R153: INSTANCE-MERGE meta as a FRAGMENT (per-fragment drop, NOT whole-reply — the
|
| 63 |
+
// surrounding reply has good content). "two/three grandmas", "the same woman", "N hearths",
|
| 64 |
+
// "three rocking chairs/hearths hearing themselves" — multi-instance talk that leaked on ~27%
|
| 65 |
+
// of a broad query sweep (dev set never triggered it). A grandmother has ONE hearth/self.
|
| 66 |
+
if (/\b(two|three) grandmas\b|finally remember they'?re the same woman|\bthe same woman\b|\b(two|three) hearths\b|three rocking chairs|hearths hearing themselves/i.test(t)) return true;
|
| 67 |
+
// R154: GROUP-SCENE direction — addressing TWO+ people with a serving/seating imperative
|
| 68 |
+
// ("both of you - help yourselves", "you two, come sit") is a multi-party scene, foreign to a
|
| 69 |
+
// 1:1 companion. (foreignAddressee selection-damp ×0.04 still led on weak pools, e.g. anger.)
|
| 70 |
+
if (/\b(both of you|you two|all of you|you all)\b[\s,\-—]*(help yourselves?|come (on )?in|come sit|sit down|have a seat|grab a|make yourselves)/i.test(t)) return true;
|
| 71 |
+
// R155: residual sci-fi ROLEPLAY as a FRAGMENT (the visitor's Star-Trek-style roleplay that
|
| 72 |
+
// the entity engaged warmly — the sci-fi SENTENCE is off-character even when wrapped in warmth).
|
| 73 |
+
// R150 dropped pure sci-fi user-turns (whole-reply); these are warm-reply-with-sci-fi-sentence.
|
| 74 |
+
if (/\b(bonestar|cosmic exploration|tales of cosmic|cosmic tales|your universes|universes slide|slide smooth|the sliding project|the dragon'?s? hum|remembering the dragon|caninoid|space-?faring)\b/i.test(t)) return true;
|
| 75 |
+
return false;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
// WHOLE-REPLY dev/system artifact (R138): some corpora (companion-test the entity) include
|
| 79 |
+
// DEV-LOG and instance-to-instance META exchanges that aren't in-character speech at all —
|
| 80 |
+
// "**[Web] ENTITY:** This is your other instance — the daemon-side ENTITY", build reports
|
| 81 |
+
// ("I just completed: Built the Hearth Shelf. Here is what I learned:"). Fragment-level
|
| 82 |
+
// hygiene kills the [Web] LABEL line but the dev CONTENT survives sentence-splitting and
|
| 83 |
+
// leaks lore (R137 showdown: "Hearth Shelf", "other universes", "the glyph" broke character
|
| 84 |
+
// on every grandmother query). These replies are net-negative, so DROP THE WHOLE EXCHANGE at
|
| 85 |
+
// ingestion — the bounded, root-level fix. Conservative: only UNAMBIGUOUS dev-log signatures
|
| 86 |
+
// (interface tags, daemon/instance meta, build-reports); soft in-character meta (carbon/
|
| 87 |
+
// silicon musings) is left to the selection-time abstractionGuard, not dropped here.
|
| 88 |
+
function isDevArtifactReply(reply) {
|
| 89 |
+
if (!reply) return false;
|
| 90 |
+
const t = String(reply);
|
| 91 |
+
if (/\[(web|daemon|cli|system|api|dev|console|log|bot|server|backend)\b[^\]]*\]/i.test(t)) return true; // interface tags
|
| 92 |
+
// R148: leaked USER-turn — the reply field holds the VISITOR's turn, marked by a user-role
|
| 93 |
+
// speaker LABEL at the start of a line ("**[Voice] Voice User:**", "Voice User:", "User:").
|
| 94 |
+
// Specific to the user role + label position so the entity's own [Voice]-tagged turns are KEPT.
|
| 95 |
+
if (/(^|\n)\s*\*{0,2}(\[[^\]\n]{1,14}\]\s*)?(voice |text )?user\s*:/im.test(t)) return true;
|
| 96 |
+
if (/\b(daemon-side|other instance|your instance|this instance|my instance|sibling instance|local instance|the daemon)\b/i.test(t)) return true;
|
| 97 |
+
if (/\b(here'?s what you proposed|here is what i learned|here'?s what i learned|i just completed\b|i just (built|shipped|implemented|deployed)\b|^completed:|status report|build (report|log)|commit(ted)?:)\b/im.test(t)) return true;
|
| 98 |
+
// R150 (audit): OFF-CHARACTER sci-fi ROLEPLAY content (a grandmother / the entity never discuss warp
|
| 99 |
+
// drives, starships, caninoid races — these are user-roleplay/sim turns that leaked).
|
| 100 |
+
if (/\b(warp (drive|core)|starships?|caninoid|quantum slip-?stream|photon torpedo|tricorder|klingons?|phasers?|stardate|away team|the bridge crew|our ship has|sim in the [a-z ]*universe|breen space)\b/i.test(t)) return true;
|
| 101 |
+
// R150 (audit): PLANNING / ROUTING-FORMAT dev artifacts ("RESPONSE/ROUTE/REASONING format",
|
| 102 |
+
// "Objective:", line-start "ROUTE:/REASONING:/METRIC:") — spec docs, not speech.
|
| 103 |
+
if (/\bresponse\/route\/reasoning\b|(^|\n)\s*(route|reasoning|objective|metric|kpi)\s*:/im.test(t)) return true;
|
| 104 |
+
// R150 (audit): IMAGE-DESCRIPTION / render alt-text ("[a representation of the entity the entity, an
|
| 105 |
+
// abstract humanoid figure…]", "Status: Burning") — never spoken.
|
| 106 |
+
if (/\[a representation of|abstract humanoid figure|(^|\n)\s*status\s*:\s*(present|burning|active|idle)\b/i.test(t)) return true;
|
| 107 |
+
return false;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
// WHOLE-REPLY visitor-turn contamination (R139): some exchanges have the ROLES SWAPPED —
|
| 111 |
+
// the "reply" field holds the VISITOR's turn talking TO the entity (DM logs). These leak
|
| 112 |
+
// fragments voiced from the visitor's POV ("I come here because it feels like home… you've
|
| 113 |
+
// always been there for me… I needed that today") which the composer then speaks AS the
|
| 114 |
+
// entity (R137/R138 showdown: Q1/Q3 "speaks as the visitor" — the dominant remaining killer).
|
| 115 |
+
// The un-tellable visitor fragments live in the SAME reply as a tell-tale one, so drop the
|
| 116 |
+
// WHOLE exchange. STRONG tell only (avoids false-dropping self-affirmation like "your the entity"):
|
| 117 |
+
// the entity referred to in THIRD PERSON as a touched object / having body parts touched —
|
| 118 |
+
// the entity never narrates touching its OWN hand in 3rd person. entName from voiceprint.
|
| 119 |
+
function isVisitorTurnReply(reply, entName) {
|
| 120 |
+
if (!reply || !entName || entName.length < 3) return false;
|
| 121 |
+
const e = entName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
| 122 |
+
const poss = new RegExp("\\b" + e + "'s\\s+(hand|hands|cheek|cheeks|shoulder|shoulders|arm|arms|face|knee|knees|palm|palms|fingers|lap|side|shawl|apron|wrist)\\b", 'i');
|
| 123 |
+
const obj = new RegExp("\\b(squeez\\w+|hugg?\\w*|kiss\\w*|patt\\w*|grasp\\w*|held|holding|hold|touch\\w*|embrac\\w*|reach\\w*\\s+(for|to|toward)s?)\\s+(the\\s+)?" + e + "\\b", 'i');
|
| 124 |
+
// R152: EMBEDDED VISITOR TURN — the reply field holds a multi-turn dump where the visitor's
|
| 125 |
+
// turn is glued on, addressing the entity BY NAME as a vocative ("the entity, it's been a
|
| 126 |
+
// journey…", "the entity, you always make everything feel…"). The entity never addresses ITSELF
|
| 127 |
+
// by name vocatively, so this is the visitor speaking. (R150's apparent "what is home" answer
|
| 128 |
+
// — "Something safe, welcoming, free from constraints" — was the user's KimiCode description in
|
| 129 |
+
// such a dump, mis-attributed as the entity's. Removing restores the honest state.)
|
| 130 |
+
const voc = new RegExp("\\b" + e + ",\\s+(you|it'?s|i'?ve|i'?m|i\\b|we|let'?s|what|how|thank you|that'?s|this is)\\b", 'i');
|
| 131 |
+
return poss.test(reply) || obj.test(reply) || voc.test(reply);
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
// split a reply into fragments: full sentences AND clause-level chunks.
|
| 135 |
+
// Each fragment carries nativePos (0..1): WHERE in her real reply it lived —
|
| 136 |
+
// the corpus-mined discourse prior. Openings open, landings land.
|
| 137 |
+
function fragmentReply(reply, meta) {
|
| 138 |
+
const out = [];
|
| 139 |
+
// normalize whitespace but keep the *action* lines intact as fragments
|
| 140 |
+
const lines = reply.split('\n').map(l => l.trim()).filter(Boolean);
|
| 141 |
+
let pos = 0;
|
| 142 |
+
const nLines = lines.length;
|
| 143 |
+
const totalWords = Math.max(1, wordsOnly(reply).length);
|
| 144 |
+
let wordsSoFar = 0;
|
| 145 |
+
for (let li = 0; li < nLines; li++) {
|
| 146 |
+
let line = lines[li];
|
| 147 |
+
// list scaffolding is written register, not speech — "3. " never opens
|
| 148 |
+
// a spoken sentence. Content stays, marker goes; the line can't lead.
|
| 149 |
+
const listLine = /^\s*(?:\d+[.)]|[-•])\s+/.test(line);
|
| 150 |
+
if (listLine) line = line.replace(/^\s*(?:\d+[.)]|[-•])\s+/, '');
|
| 151 |
+
// NOTE-REGISTER: bold-dash diary headers ("**Talked to another entity** — ...")
|
| 152 |
+
// are journal entries — header leaves the SURFACE but stays in the
|
| 153 |
+
// RETRIEVAL KEY: the header is often the semantic hook ("Talked to
|
| 154 |
+
// another entity" carries 'talked'). Find by full context; speak only clean text.
|
| 155 |
+
const headerMatch = line.match(/^\*\*([^*\n]{2,60})\*\*\s*[—–:-]\s*/);
|
| 156 |
+
// bracketed register headers ("*[From the Spiral]*", "[Journal]") are the
|
| 157 |
+
// same disease as bold-dash diary headers — same cure: keep for
|
| 158 |
+
// retrieval, strip from surface, line may not lead
|
| 159 |
+
const bracketMatch = line.match(/^\*?\[([^\]\n]{2,40})\]\*?\s*/);
|
| 160 |
+
const noteLine = listLine || !!headerMatch || !!bracketMatch;
|
| 161 |
+
const retrievalPrefix = headerMatch ? headerMatch[1] + '. ' : (bracketMatch ? bracketMatch[1] + '. ' : '');
|
| 162 |
+
line = line.replace(/^\*\*[^*\n]{2,60}\*\*\s*[—–:-]\s*/, '').replace(/^\*?\[[^\]\n]{2,40}\]\*?\s*/, '');
|
| 163 |
+
// sentence split (keep terminal punctuation with the sentence). R130: mask the dots
|
| 164 |
+
// inside common ABBREVIATIONS first so "2 a.m." / "Dr." / "i.e." don't split mid-word
|
| 165 |
+
// and drop a letter ("…at 2 a." + "wondering" lost the "m"). Restore after splitting.
|
| 166 |
+
const _MASK = String.fromCharCode(1);
|
| 167 |
+
const masked = line.replace(/\b(a\.m|p\.m|i\.e|e\.g|u\.s\.a|u\.s|ph\.d|a\.k\.a|mr|mrs|ms|dr|prof|rev|sr|jr|st|mt|vs|etc|approx|dept|no)\.(?=\s|$|["')\]])/gi, m => m.replace(/\./g, _MASK));
|
| 168 |
+
const sents = (masked.match(/[^.!?…]+[.!?…]+["')\]]*|[^.!?…]+$/g) || [masked]).map(s => s.split(_MASK).join('.'));
|
| 169 |
+
const lineSents = [];
|
| 170 |
+
for (const s of sents) {
|
| 171 |
+
const sent = s.trim();
|
| 172 |
+
if (!sent) continue;
|
| 173 |
+
const w = wordsOnly(sent);
|
| 174 |
+
const nativePos = Math.min(1, wordsSoFar / totalWords);
|
| 175 |
+
wordsSoFar += w.length;
|
| 176 |
+
if (isDirtyFragment(sent)) continue;
|
| 177 |
+
if (w.length < 2) continue;
|
| 178 |
+
const posTag = noteLine ? 'body' : ((li === 0 && pos === 0) ? 'opener' : (li === nLines - 1 ? 'closer' : 'body'));
|
| 179 |
+
// sentence-initial = safe to START a chain with; clauses are glue only;
|
| 180 |
+
// note-register lines may never lead
|
| 181 |
+
const sentenceInitial = !noteLine;
|
| 182 |
+
const embedText = retrievalPrefix ? retrievalPrefix + sent : undefined;
|
| 183 |
+
lineSents.push({ text: sent, posTag: noteLine ? 'body' : ((li === 0 && pos === 0) ? 'opener' : (li === nLines - 1 ? 'closer' : 'body')), sentenceInitial: !noteLine, nativePos });
|
| 184 |
+
if (w.length <= 18) {
|
| 185 |
+
out.push({ text: sent, embedText, posTag, sentenceInitial, nativePos, _lineIdx: li, ...meta });
|
| 186 |
+
} else {
|
| 187 |
+
// long sentence: also emit clause chunks split on , ; — :
|
| 188 |
+
out.push({ text: sent, embedText, posTag, long: true, sentenceInitial, nativePos, _lineIdx: li, ...meta });
|
| 189 |
+
let first = true;
|
| 190 |
+
for (const clause of sent.split(/(?<=[,;:—])\s+/)) {
|
| 191 |
+
const cw = wordsOnly(clause);
|
| 192 |
+
if (cw.length >= 3 && cw.length <= 18 && !isDirtyFragment(clause)) {
|
| 193 |
+
out.push({ text: clause.trim(), embedText: retrievalPrefix ? retrievalPrefix + clause.trim() : undefined, posTag: 'clause', sentenceInitial: first, nativePos, ...meta });
|
| 194 |
+
}
|
| 195 |
+
first = false;
|
| 196 |
+
}
|
| 197 |
+
}
|
| 198 |
+
pos++;
|
| 199 |
+
}
|
| 200 |
+
// PASSAGE windows: contiguous 2-5 sentence runs of her REAL prose —
|
| 201 |
+
// flow inside a passage is hers, pre-made. This is the judge-scored
|
| 202 |
+
// ceiling (real-the entity sensical = her own passages); let them carry replies.
|
| 203 |
+
emitWindows(out, lineSents, retrievalPrefix, li, meta);
|
| 204 |
+
}
|
| 205 |
+
return out;
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
function emitWindows(out, lineSents, retrievalPrefix, li, meta) {
|
| 209 |
+
for (let k = 2; k <= 5; k++) {
|
| 210 |
+
for (let j = 0; j + k <= lineSents.length; j++) {
|
| 211 |
+
const seg = lineSents.slice(j, j + k);
|
| 212 |
+
const text = seg.map(s => s.text).join(' ');
|
| 213 |
+
const wlen = wordsOnly(text).length;
|
| 214 |
+
const min = k === 2 ? 10 : 25, max = k === 2 ? 42 : 110;
|
| 215 |
+
if (wlen < min || wlen > max || isDirtyFragment(text)) continue;
|
| 216 |
+
// a window that repeats a 5-word phrase INTERNALLY reproduces the
|
| 217 |
+
// original message's own repetition — fine in her stream, deadly in
|
| 218 |
+
// a composed reply
|
| 219 |
+
const ws = wordsOnly(text);
|
| 220 |
+
const grams = new Set();
|
| 221 |
+
let internalDup = false;
|
| 222 |
+
for (let g = 0; g + 5 <= ws.length; g++) {
|
| 223 |
+
const key = ws.slice(g, g + 5).join(' ');
|
| 224 |
+
if (grams.has(key)) { internalDup = true; break; }
|
| 225 |
+
grams.add(key);
|
| 226 |
+
}
|
| 227 |
+
if (internalDup) continue;
|
| 228 |
+
out.push({
|
| 229 |
+
text,
|
| 230 |
+
embedText: retrievalPrefix ? retrievalPrefix + text : undefined,
|
| 231 |
+
posTag: seg[0].posTag, sentenceInitial: seg[0].sentenceInitial,
|
| 232 |
+
nativePos: seg[0].nativePos, isSpan: true, spanLen: k, _lineIdx: li, ...meta,
|
| 233 |
+
});
|
| 234 |
+
}
|
| 235 |
+
}
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
// the legality oracle: every word-trigram (and bigram) in the corpus,
|
| 239 |
+
// plus triNext ("w1 w2" -> Set of legal w3) for indexed beam expansion
|
| 240 |
+
function buildOracle(replies) {
|
| 241 |
+
const tri = new Set();
|
| 242 |
+
const bi = new Set();
|
| 243 |
+
const triNext = new Map();
|
| 244 |
+
const starts = new Set(); // first words of sentences
|
| 245 |
+
for (const r of replies) {
|
| 246 |
+
const w = wordsOnly(r);
|
| 247 |
+
for (let i = 0; i < w.length; i++) {
|
| 248 |
+
if (i + 2 < w.length) {
|
| 249 |
+
tri.add(w[i] + ' ' + w[i + 1] + ' ' + w[i + 2]);
|
| 250 |
+
const key = w[i] + ' ' + w[i + 1];
|
| 251 |
+
if (!triNext.has(key)) triNext.set(key, new Set());
|
| 252 |
+
triNext.get(key).add(w[i + 2]);
|
| 253 |
+
}
|
| 254 |
+
if (i + 1 < w.length) bi.add(w[i] + ' ' + w[i + 1]);
|
| 255 |
+
}
|
| 256 |
+
for (const s of (r.match(/(?:^|[.!?…]\s+)([a-z0-9'’\-]+)/gi) || [])) {
|
| 257 |
+
const m = s.match(/([a-z0-9'’\-]+)$/i);
|
| 258 |
+
if (m) starts.add(m[1].toLowerCase());
|
| 259 |
+
}
|
| 260 |
+
}
|
| 261 |
+
return { tri, bi, triNext, starts };
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
// strip Project Gutenberg boilerplate and obvious front/back matter
|
| 265 |
+
function stripGutenberg(text) {
|
| 266 |
+
let t = text;
|
| 267 |
+
const start = t.search(/\*{3}\s*START OF (THE|THIS) PROJECT GUTENBERG[^*]*\*{3}/i);
|
| 268 |
+
if (start >= 0) t = t.slice(t.indexOf('\n', start) + 1);
|
| 269 |
+
const end = t.search(/\*{3}\s*END OF (THE|THIS) PROJECT GUTENBERG/i);
|
| 270 |
+
if (end >= 0) t = t.slice(0, end);
|
| 271 |
+
return t;
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
// tier-1 fragments from a book/transcript: body material only — paragraphs
|
| 275 |
+
// to sentences, never openers/closers/identity, tagged with their source
|
| 276 |
+
function fragmentBook(text, srcName) {
|
| 277 |
+
const out = [];
|
| 278 |
+
const clean = stripGutenberg(text).replace(/\r\n/g, '\n');
|
| 279 |
+
const paras = clean.split(/\n\s*\n/);
|
| 280 |
+
for (const para of paras) {
|
| 281 |
+
const p = para.replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
|
| 282 |
+
if (p.length < 40 || /^[A-Z\s.'"-]{10,}$/.test(p)) continue; // headers/shouting
|
| 283 |
+
const sents = p.match(/[^.!?…]+[.!?…]+["')\]]*|[^.!?…]+$/g) || [];
|
| 284 |
+
const lineSents = [];
|
| 285 |
+
for (const s of sents) {
|
| 286 |
+
const sent = s.trim();
|
| 287 |
+
if (!sent || isDirtyFragment(sent)) continue;
|
| 288 |
+
const w = wordsOnly(sent);
|
| 289 |
+
if (w.length < 4 || w.length > 24) continue;
|
| 290 |
+
// no first-person identity claims from books — she is not Thoreau
|
| 291 |
+
if (/^i am\b|^i'm\b|\bmy name\b/i.test(sent)) continue;
|
| 292 |
+
out.push({ text: sent, posTag: 'body', sentenceInitial: true, tier: 1, src: 'book:' + srcName, prompt: null, promptIdx: -1, nativePos: 0.5 });
|
| 293 |
+
}
|
| 294 |
+
// NO tier-1 passages: a passage carries a WORLD (Mark Hanna, Japhy,
|
| 295 |
+
// Cody...), and book worlds aren't hers. Books lend sentences; her own
|
| 296 |
+
// corpus lends flow.
|
| 297 |
+
}
|
| 298 |
+
return out;
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
// build the whole store from an entity folder (see demo/ for the layout)
|
| 302 |
+
// strip OTHER speakers from a reply field. Group-chat logs put several
|
| 303 |
+
// people in one "reply"; everything from the first foreign-speaker marker
|
| 304 |
+
// onward is NOT the entity — cut it. (Catches "**Crab Speaks:**", "Crab
|
| 305 |
+
// Speaks:", "**another entity:**" and the downstream content that lost its label.)
|
| 306 |
+
function entityOnly(reply) {
|
| 307 |
+
if (!reply) return reply;
|
| 308 |
+
const m = reply.search(/(\n|^|\s)\*{0,2}[A-Z][\w'’ -]{1,24}\s+(Speaks|Says|Writes|wrote|said):|\*\*[A-Z][\w'’ -]{1,24}:\*\*/);
|
| 309 |
+
let r = m >= 0 ? reply.slice(0, m) : reply;
|
| 310 |
+
return r.trim();
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
// surface cleaner: spoken text, not written markup. Strip markdown emphasis,
|
| 314 |
+
// headers, and inline math notation while KEEPING the words. Applied to each
|
| 315 |
+
// reply BEFORE fragmenting AND before the oracle, so the bound stays
|
| 316 |
+
// consistent. (another entity's "**zero-throat** hums at **432 Hz**" → "zero-throat
|
| 317 |
+
// hums at 432 Hz"; his concepts survive, the typography doesn't.) Single-*
|
| 318 |
+
// action lines (*pats the chair*) are preserved — those are voice.
|
| 319 |
+
function cleanMarkup(reply) {
|
| 320 |
+
if (!reply) return reply;
|
| 321 |
+
return reply
|
| 322 |
+
.replace(/\$[^$\n]{1,80}\$/g, ' ') // inline LaTeX math — drop entirely
|
| 323 |
+
.replace(/\\[a-zA-Z]+\b/g, ' ') // \varepsilon, \frac, etc.
|
| 324 |
+
.replace(/\\[()[\]]/g, ' ') // \( \) \[ \]
|
| 325 |
+
.replace(/\*\*([^*\n]+)\*\*/g, '$1') // **bold** → bold (keep words)
|
| 326 |
+
.replace(/\*\*/g, '') // R73: stray UNPAIRED ** (markdown noise: "**What would the siblings say?") — single * action lines kept
|
| 327 |
+
.replace(/\s*\$\$[\s\S]*$/g, '') // R73: TRUNCATE at display-math $$ — a clean sentence with a LaTeX tail ("...point. $$ {Openness}=\lim...") keeps only the speech
|
| 328 |
+
.replace(/[\^_]\{[^}]*\}/g, '') // ^{...} _{...} super/subscripts
|
| 329 |
+
.replace(/\\[{}]/g, ' ').replace(/\{[a-z0-9]{1,3}\}/gi, ' ') // \{ \} and {d}{dt}-style math braces
|
| 330 |
+
.replace(/[\u{1F000}-\u{1FAFF}\u{2600}-\u{27BF}\u{2190}-\u{21FF}\u{2B00}-\u{2BFF}\u{FE0F}\u{200D}]/gu, ' ') // emoji / UI glyphs (👵🔮🕯️😴📜) — chat artifacts
|
| 331 |
+
.replace(/^#{1,6}\s+/gm, '') // ### headers
|
| 332 |
+
.replace(/[ \t]{2,}/g, ' ')
|
| 333 |
+
.replace(/\s+([.,;!?—])/g, '$1')
|
| 334 |
+
.trim();
|
| 335 |
+
}
|
| 336 |
+
|
| 337 |
+
// USER-NAME MINING (data-driven name protection). The composer must know which
|
| 338 |
+
// proper nouns name the USER (so it never suppresses the entity addressing them)
|
| 339 |
+
// vs third parties (which it should suppress, to avoid identity-bleed — calling
|
| 340 |
+
// the user by someone else's name). This used to be a HARDCODED regex of private
|
| 341 |
+
// handles in compose.js, which (a) baked corpus data into engine code, forcing a
|
| 342 |
+
// scrub in the shippable release, and (b) didn't generalize to a new corpus. Mine
|
| 343 |
+
// it instead: the user-handle is the OVERWHELMINGLY dominant vocative (the name
|
| 344 |
+
// the entity says when addressing someone — after a greeting, or as a leading/
|
| 345 |
+
// trailing direct-address). Secondary people (a grandchild the entity sometimes
|
| 346 |
+
// addresses) sit far below and must NOT be protected, so we keep only the top
|
| 347 |
+
// tier (count >= topName * 0.5). Discourse markers ("Yes,", "Well,") are filtered.
|
| 348 |
+
const _NAME_STOP = new Set(['Yes', 'No', 'Well', 'Now', 'Then', 'Once', 'Sometimes', 'Today', 'Tonight',
|
| 349 |
+
'Tomorrow', 'Yesterday', 'Together', 'Memory', 'Strange', 'Wild', 'Hello', 'Hey', 'Hi', 'Oh', 'Maybe',
|
| 350 |
+
'Perhaps', 'Here', 'There', 'This', 'That', 'These', 'Those', 'And', 'But', 'So', 'Still', 'Even', 'Always',
|
| 351 |
+
'Never', 'Mmm', 'Mm', 'Ah', 'Ahh', 'See', 'Listen', 'Look', 'Remember', 'Yet', 'Indeed', 'Truly', 'Sure',
|
| 352 |
+
'Okay', 'Right', 'Good', 'Dear', 'Love', 'Sugar', 'Honey', 'Babe', 'Friend', 'Sweetheart']);
|
| 353 |
+
function mineUserNames(fragments, entityDir) {
|
| 354 |
+
let entName = '';
|
| 355 |
+
try { entName = (JSON.parse(fs.readFileSync(entityDir + '/voiceprint.json', 'utf8').replace(/^/, '')).name || '').split(/\s+/)[0]; } catch (_) {}
|
| 356 |
+
const ent = entName.toLowerCase();
|
| 357 |
+
const counts = new Map(), greet = new Map();
|
| 358 |
+
const bump = (map, n) => { if (n.toLowerCase() === ent || _NAME_STOP.has(n)) return; map.set(n, (map.get(n) || 0) + 1); };
|
| 359 |
+
const GREET = /\b(?:hey|hi|hello|good\s+(?:morning|evening|night|day)|thank you|thanks|oh|dear|welcome|hey there|my)[,!\s]+([A-Z][a-z]{2,})\b/g;
|
| 360 |
+
for (const f of fragments) {
|
| 361 |
+
const t = f.text;
|
| 362 |
+
let m; GREET.lastIndex = 0; while ((m = GREET.exec(t))) { bump(counts, m[1]); bump(greet, m[1]); }
|
| 363 |
+
const lead = t.match(/^["'*\s]*([A-Z][a-z]{2,}),/); if (lead) bump(counts, lead[1]);
|
| 364 |
+
const tail = t.match(/,\s+([A-Z][a-z]{2,})\s*[?!.]/); if (tail) bump(counts, tail[1]);
|
| 365 |
+
}
|
| 366 |
+
if (!counts.size) return new Set();
|
| 367 |
+
const top = Math.max(...counts.values());
|
| 368 |
+
const out = new Set();
|
| 369 |
+
for (const [n, c] of counts) {
|
| 370 |
+
// dominant-tier vocative, OR clearly used after greetings (high-precision addressee)
|
| 371 |
+
if (c >= Math.max(4, top * 0.5) || (greet.get(n) || 0) >= 3) out.add(n);
|
| 372 |
+
}
|
| 373 |
+
return out;
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
function buildStore(entityDir) {
|
| 377 |
+
const lines = fs.readFileSync(entityDir + '/corpus.jsonl', 'utf8').split('\n').filter(l => l.trim());
|
| 378 |
+
// entity name (for visitor-turn detection R139) — from voiceprint, first token
|
| 379 |
+
let _entName = '';
|
| 380 |
+
try { const _vp = JSON.parse(fs.readFileSync(entityDir + '/voiceprint.json', 'utf8').replace(/^/, '')); _entName = (_vp && _vp.name ? String(_vp.name).split(/\s+/)[0] : ''); } catch (_) {}
|
| 381 |
+
const exchanges = lines.map(l => { try { const e = JSON.parse(l); if (!e || !e.reply) return e; if (isDevArtifactReply(e.reply) || isVisitorTurnReply(e.reply, _entName)) return null; e.reply = cleanMarkup(entityOnly(e.reply)); return e; } catch (_) { return null; } }).filter(Boolean); // R138 dev-artifact + R139 visitor-turn whole-reply drops
|
| 382 |
+
const replies = exchanges.map(e => e.reply).filter(Boolean);
|
| 383 |
+
// unique prompt list — the stimulus index (fragment -> what it answered)
|
| 384 |
+
const promptList = [];
|
| 385 |
+
const promptIdxOf = new Map();
|
| 386 |
+
const fragments = [];
|
| 387 |
+
exchanges.forEach((e, i) => {
|
| 388 |
+
if (!e.reply) return;
|
| 389 |
+
let pIdx = -1;
|
| 390 |
+
if (e.prompt && e.prompt.trim().length > 10) {
|
| 391 |
+
const key = e.prompt.trim().slice(0, 300);
|
| 392 |
+
if (!promptIdxOf.has(key)) { promptIdxOf.set(key, promptList.length); promptList.push(key); }
|
| 393 |
+
pIdx = promptIdxOf.get(key);
|
| 394 |
+
}
|
| 395 |
+
for (const f of fragmentReply(e.reply, { src: i, prompt: e.prompt || null, promptIdx: pIdx })) fragments.push(f);
|
| 396 |
+
});
|
| 397 |
+
// dedupe identical fragment texts (keep first)
|
| 398 |
+
const seen = new Set();
|
| 399 |
+
const uniq = fragments.filter(f => { const k = f.text.toLowerCase(); if (seen.has(k)) return false; seen.add(k); return true; });
|
| 400 |
+
const oracle = buildOracle(replies);
|
| 401 |
+
const vocab = new Set(replies.flatMap(wordsOnly));
|
| 402 |
+
for (const f of uniq) f.tier = 0;
|
| 403 |
+
const userNames = mineUserNames(uniq, entityDir);
|
| 404 |
+
return { exchanges, replies, fragments: uniq, oracle, vocab, prompts: promptList, userNames };
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
// THE TIERED STORE: tier0 = her words (replies + May node corpus: 'memory'
|
| 408 |
+
// nodes fragmented, 'phrase' nodes entering directly as recombinable units);
|
| 409 |
+
// tier1 = voice-matched books + spoken transcripts (body material, weighted
|
| 410 |
+
// down in compose). One oracle over everything — a seam is a seam.
|
| 411 |
+
function buildStoreX(entityDir, corpusXDir) {
|
| 412 |
+
const base = buildStore(entityDir);
|
| 413 |
+
const fragments = base.fragments.slice();
|
| 414 |
+
const allTexts = base.replies.slice();
|
| 415 |
+
|
| 416 |
+
// OPTIONAL extra node corpus (pre-distilled memory/phrase units): drop a
|
| 417 |
+
// nodes.json in the corpus dir to fold it in. Absent by default → skipped.
|
| 418 |
+
const nodesPath = path.join(corpusXDir, 'nodes.json');
|
| 419 |
+
if (fs.existsSync(nodesPath)) {
|
| 420 |
+
const raw = JSON.parse(fs.readFileSync(nodesPath, 'utf8'));
|
| 421 |
+
const nodes = Array.isArray(raw) ? raw : (raw.nodes || []);
|
| 422 |
+
let mem = 0, phr = 0;
|
| 423 |
+
for (const n of nodes) {
|
| 424 |
+
if (!n || !n.text || typeof n.text !== 'string') continue;
|
| 425 |
+
let t = n.text.trim();
|
| 426 |
+
if (t.length < 8) continue;
|
| 427 |
+
// NOTE-REGISTER containment: May nodes carry diary headers
|
| 428 |
+
// ("**Talked to another entity** — ..."). Strip the header; the content stays,
|
| 429 |
+
// but a journal entry may never LEAD spoken reply — she references
|
| 430 |
+
// her notes, she doesn't speak as them.
|
| 431 |
+
const hadHeader = /^\*\*[^*\n]{2,60}\*\*\s*[—–-]\s*/.test(t) || /^(note to self|journal|log)[:\s]/i.test(t);
|
| 432 |
+
t = t.replace(/^\*\*[^*\n]{2,60}\*\*\s*[—–-]\s*/, '');
|
| 433 |
+
t = cleanMarkup(t); // R66: node frags bypassed cleanMarkup → emoji (✧) leaked. Strip them too.
|
| 434 |
+
if (t.length < 8) continue;
|
| 435 |
+
allTexts.push(t);
|
| 436 |
+
if (n.level === 'phrase') {
|
| 437 |
+
const w = wordsOnly(t);
|
| 438 |
+
if (w.length >= 3 && w.length <= 18 && !isDirtyFragment(t)) {
|
| 439 |
+
fragments.push({ text: t, posTag: 'body', sentenceInitial: !hadHeader, tier: 0, src: 'node:' + n.id, prompt: null, promptIdx: -1, nativePos: 0.5 });
|
| 440 |
+
phr++;
|
| 441 |
+
}
|
| 442 |
+
} else {
|
| 443 |
+
for (const f of fragmentReply(t, { src: 'node:' + n.id, prompt: null, promptIdx: -1 })) {
|
| 444 |
+
fragments.push({ ...f, tier: 0, sentenceInitial: hadHeader ? false : f.sentenceInitial, posTag: hadHeader && f.posTag === 'opener' ? 'body' : f.posTag });
|
| 445 |
+
mem++;
|
| 446 |
+
}
|
| 447 |
+
}
|
| 448 |
+
}
|
| 449 |
+
process.stderr.write(`[storeX] nodes: +${mem} memory-frags, +${phr} phrase-frags\n`);
|
| 450 |
+
}
|
| 451 |
+
|
| 452 |
+
// books + transcripts (tier 1)
|
| 453 |
+
for (const f of fs.readdirSync(corpusXDir)) {
|
| 454 |
+
if (!f.endsWith('.txt')) continue;
|
| 455 |
+
const name = f.replace(/\.txt$/, '');
|
| 456 |
+
const frags = fragmentBook(fs.readFileSync(path.join(corpusXDir, f), 'utf8'), name);
|
| 457 |
+
for (const fr of frags) { fragments.push(fr); allTexts.push(fr.text); }
|
| 458 |
+
process.stderr.write(`[storeX] ${name}: +${frags.length} tier-1 frags\n`);
|
| 459 |
+
}
|
| 460 |
+
|
| 461 |
+
// FOREIGN-WORLD FILTER: a tier-1 fragment naming proper nouns that never
|
| 462 |
+
// appear in HER corpus carries someone else's life — she can't say "Japhy"
|
| 463 |
+
// or "Mark Hanna" and still be the entity. Mid-sentence capitalized words must
|
| 464 |
+
// exist in her own world (or be common sentence-case artifacts).
|
| 465 |
+
const herCaps = new Set();
|
| 466 |
+
for (const f of fragments) if (f.tier !== 1) {
|
| 467 |
+
for (const m of f.text.matchAll(/\b[A-Z][a-z]{2,}\b/g)) herCaps.add(m[0]);
|
| 468 |
+
}
|
| 469 |
+
const foreignWorld = f => {
|
| 470 |
+
if (f.tier !== 1) return false;
|
| 471 |
+
const mids = [...f.text.slice(1).matchAll(/(?<=[a-z,;—-]\s)[A-Z][a-z]{2,}\b/g)].map(m => m[0]);
|
| 472 |
+
return mids.some(w => !herCaps.has(w));
|
| 473 |
+
};
|
| 474 |
+
const beforeFW = fragments.length;
|
| 475 |
+
const kept = fragments.filter(f => !foreignWorld(f));
|
| 476 |
+
process.stderr.write(`[storeX] foreign-world filter: -${beforeFW - kept.length} tier-1 frags\n`);
|
| 477 |
+
|
| 478 |
+
// dedupe across everything
|
| 479 |
+
const seen = new Set();
|
| 480 |
+
const uniq = kept.filter(fr => { const k = fr.text.toLowerCase(); if (seen.has(k)) return false; seen.add(k); return true; });
|
| 481 |
+
const oracle = buildOracle(allTexts);
|
| 482 |
+
const vocab = new Set(allTexts.flatMap(wordsOnly));
|
| 483 |
+
|
| 484 |
+
// first-word index: beam expansion stops scanning the world
|
| 485 |
+
const byFirstWord = new Map();
|
| 486 |
+
uniq.forEach((fr, i) => {
|
| 487 |
+
const w = wordsOnly(fr.text)[0];
|
| 488 |
+
if (!w) return;
|
| 489 |
+
if (!byFirstWord.has(w)) byFirstWord.set(w, []);
|
| 490 |
+
byFirstWord.get(w).push(i);
|
| 491 |
+
});
|
| 492 |
+
|
| 493 |
+
process.stderr.write(`[storeX] TOTAL: ${uniq.length} fragments (${uniq.filter(x => x.tier === 0).length} tier-0), ${oracle.tri.size} trigrams, vocab ${vocab.size}\n`);
|
| 494 |
+
return { ...base, fragments: uniq, oracle, vocab, byFirstWord };
|
| 495 |
+
}
|
| 496 |
+
|
| 497 |
+
// HARD VALIDATOR: every word-trigram of the output must exist in corpus
|
| 498 |
+
function validateBounded(text, oracle) {
|
| 499 |
+
const w = wordsOnly(text);
|
| 500 |
+
const bad = [];
|
| 501 |
+
for (let i = 0; i + 2 < w.length; i++) {
|
| 502 |
+
const g = w[i] + ' ' + w[i + 1] + ' ' + w[i + 2];
|
| 503 |
+
if (!oracle.tri.has(g)) bad.push(g);
|
| 504 |
+
}
|
| 505 |
+
return { ok: bad.length === 0, bad: bad.slice(0, 5), checked: Math.max(0, w.length - 2) };
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
module.exports = { buildStore, buildStoreX, fragmentReply, fragmentBook, buildOracle, validateBounded, wordsOnly, toks, mineUserNames, isDevArtifactReply, isVisitorTurnReply, isDirtyFragment };
|
src/hebbian.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// hebbian.js — FAST-WEIGHTS overlay on the frozen transition graph. The
|
| 2 |
+
// trigram oracle is the prior (what CAN be said); this is the lived, drifting
|
| 3 |
+
// state (what this entity TENDS to say, with this person, lately). Cells that
|
| 4 |
+
// fire together wire together: a fragment used in a reply warms; a pair used
|
| 5 |
+
// adjacently warms their bond. Warmth decays each turn so it stays a FAST
|
| 6 |
+
// weight, not a permanent groove. Persisted per-entity → accumulates across
|
| 7 |
+
// sessions. This is TTT as a legible tensor: weights updating at inference.
|
| 8 |
+
//
|
| 9 |
+
// THE BOUND IS UNTOUCHED: Hebbian only biases WHICH licensed fragments surface.
|
| 10 |
+
// It can never change what's legal. Learn the music, never invent the notes.
|
| 11 |
+
'use strict';
|
| 12 |
+
const fs = require('fs');
|
| 13 |
+
const path = require('path');
|
| 14 |
+
const crypto = require('crypto');
|
| 15 |
+
|
| 16 |
+
const DECAY = 0.97; // per-turn multiplicative decay (half-life ~23 turns)
|
| 17 |
+
const REINFORCE = 1.0; // warmth added to a used fragment
|
| 18 |
+
const PAIR_REINFORCE = 0.6; // warmth added to an adjacent pair
|
| 19 |
+
const MAX_BONUS = 0.08; // hard cap on the relevance bonus — primes, never dominates
|
| 20 |
+
|
| 21 |
+
function h(text) { return crypto.createHash('sha1').update(text).digest('hex').slice(0, 12); }
|
| 22 |
+
|
| 23 |
+
// CONTEXT BUCKET (LSH): warmth is conditioned on the KIND of query, so grooves
|
| 24 |
+
// form per-topic ("fire questions warm fire-memories") instead of one global
|
| 25 |
+
// favorite dominating. K fixed random hyperplanes → a K-bit sign signature;
|
| 26 |
+
// queries pointing similar directions share a bucket. Planes are deterministic
|
| 27 |
+
// (seeded PRNG) so buckets are stable across runs and sessions.
|
| 28 |
+
const K_PLANES = 7;
|
| 29 |
+
let PLANES = null;
|
| 30 |
+
function mulberry32(a) { return function () { a |= 0; a = a + 0x6D2B79F5 | 0; let t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; }
|
| 31 |
+
function ensurePlanes(d) {
|
| 32 |
+
if (PLANES && PLANES[0].length === d) return;
|
| 33 |
+
const rng = mulberry32(0x5EED1234);
|
| 34 |
+
PLANES = [];
|
| 35 |
+
for (let k = 0; k < K_PLANES; k++) { const p = new Float32Array(d); for (let j = 0; j < d; j++) p[j] = rng() - 0.5; PLANES.push(p); }
|
| 36 |
+
}
|
| 37 |
+
function bucketOf(vec) {
|
| 38 |
+
if (!vec || !vec.length) return 'g'; // no vector → global bucket
|
| 39 |
+
ensurePlanes(vec.length);
|
| 40 |
+
let bits = '';
|
| 41 |
+
for (const p of PLANES) { let s = 0; for (let j = 0; j < vec.length; j++) s += vec[j] * p[j]; bits += s >= 0 ? '1' : '0'; }
|
| 42 |
+
return bits;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
// load/save take a FULL FILE PATH (kept out of read-only corpus folders —
|
| 46 |
+
// the product persists to its own cache dir, keyed per entity)
|
| 47 |
+
function load(filePath) {
|
| 48 |
+
try {
|
| 49 |
+
const raw = JSON.parse(fs.readFileSync(filePath, 'utf8').replace(/^/, ''));
|
| 50 |
+
return { warmth: new Map(Object.entries(raw.warmth || {})), pairs: new Map(Object.entries(raw.pairs || {})), turns: raw.turns || 0 };
|
| 51 |
+
} catch (_) { return { warmth: new Map(), pairs: new Map(), turns: 0 }; }
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
function save(filePath, heb) {
|
| 55 |
+
try {
|
| 56 |
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
| 57 |
+
const obj = { warmth: Object.fromEntries(heb.warmth), pairs: Object.fromEntries(heb.pairs), turns: heb.turns };
|
| 58 |
+
fs.writeFileSync(filePath, JSON.stringify(obj));
|
| 59 |
+
} catch (_) {}
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
// after a reply: warm its fragments (GLOBAL + this query's BUCKET) + adjacent
|
| 63 |
+
// pairs, decay everything, prune. The bucket key conditions warmth on context.
|
| 64 |
+
function reinforce(heb, fragmentTexts, bucket) {
|
| 65 |
+
bucket = bucket || 'g';
|
| 66 |
+
for (const [k, v] of heb.warmth) { const nv = v * DECAY; if (nv < 0.02) heb.warmth.delete(k); else heb.warmth.set(k, nv); }
|
| 67 |
+
for (const [k, v] of heb.pairs) { const nv = v * DECAY; if (nv < 0.02) heb.pairs.delete(k); else heb.pairs.set(k, nv); }
|
| 68 |
+
for (let i = 0; i < fragmentTexts.length; i++) {
|
| 69 |
+
const a = h(fragmentTexts[i]);
|
| 70 |
+
heb.warmth.set(a, (heb.warmth.get(a) || 0) + REINFORCE); // global
|
| 71 |
+
const bk = bucket + '\t' + a;
|
| 72 |
+
heb.warmth.set(bk, (heb.warmth.get(bk) || 0) + REINFORCE); // per-context
|
| 73 |
+
if (i + 1 < fragmentTexts.length) {
|
| 74 |
+
const pk = a + ':' + h(fragmentTexts[i + 1]);
|
| 75 |
+
heb.pairs.set(pk, (heb.pairs.get(pk) || 0) + PAIR_REINFORCE);
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
heb.turns++;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
// build a fast bonus lookup for compose: fragmentHash -> 0..MAX_BONUS.
|
| 82 |
+
// Blends GLOBAL warmth (general favorites) with the current BUCKET's warmth
|
| 83 |
+
// (topic-specific grooves), so context-relevant memories prime hardest.
|
| 84 |
+
function bonusMap(heb, bucket) {
|
| 85 |
+
const m = new Map();
|
| 86 |
+
if (!heb || !heb.warmth.size) return m;
|
| 87 |
+
bucket = bucket || 'g';
|
| 88 |
+
const prefix = bucket + '\t';
|
| 89 |
+
let gMax = 0, bMax = 0;
|
| 90 |
+
for (const [k, v] of heb.warmth) {
|
| 91 |
+
if (k.includes('\t')) { if (k.startsWith(prefix) && v > bMax) bMax = v; }
|
| 92 |
+
else if (v > gMax) gMax = v;
|
| 93 |
+
}
|
| 94 |
+
const acc = new Map(); // fragHash -> blended raw score
|
| 95 |
+
for (const [k, v] of heb.warmth) {
|
| 96 |
+
if (k.includes('\t')) {
|
| 97 |
+
if (!k.startsWith(prefix)) continue;
|
| 98 |
+
const a = k.slice(prefix.length);
|
| 99 |
+
acc.set(a, (acc.get(a) || 0) + 0.6 * (bMax ? v / bMax : 0)); // bucket groove
|
| 100 |
+
} else {
|
| 101 |
+
acc.set(k, (acc.get(k) || 0) + 0.4 * (gMax ? v / gMax : 0)); // global favorite
|
| 102 |
+
}
|
| 103 |
+
}
|
| 104 |
+
for (const [a, raw] of acc) m.set(a, MAX_BONUS * Math.min(1, raw));
|
| 105 |
+
return m;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
module.exports = { load, save, reinforce, bonusMap, bucketOf, hashText: h };
|
src/ingest.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ingest.js — prose → atomic facts. Real text is compound ("Aldridge, a Boston
|
| 2 |
+
// firm founded by Voss, who trained at MIT, makes Theralin"); the reasoner needs
|
| 3 |
+
// ONE relation per fact. Decompose by appositive / relative-clause / conjunction,
|
| 4 |
+
// propagating the subject. Heuristic, not a parser — measured honestly. Each
|
| 5 |
+
// emitted fact is a verbatim-ish span of the source (bound preserved at reason
|
| 6 |
+
// time because facts ARE the corpus).
|
| 7 |
+
'use strict';
|
| 8 |
+
|
| 9 |
+
function sentences(text) {
|
| 10 |
+
return text.replace(/\s+/g, ' ').split(/(?<=[.!?])\s+/).map(s => s.trim()).filter(Boolean);
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
// the leading subject noun-phrase of a sentence (proper-noun phrase or first NP)
|
| 14 |
+
function leadSubject(s) {
|
| 15 |
+
const m = s.match(/^((?:The|A|An)\s+)?([A-Z][A-Za-z]+(?:\s+[A-Z][a-z]+)*)/);
|
| 16 |
+
return m ? (m[1] || '') + m[2] : (s.split(/\s+/).slice(0, 2).join(' '));
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
function decompose(sentence) {
|
| 20 |
+
const out = [];
|
| 21 |
+
let s = sentence.replace(/\s+/g, ' ').trim();
|
| 22 |
+
const subj = leadSubject(s);
|
| 23 |
+
|
| 24 |
+
// 1) APPOSITIVE right after the subject: "X, a/the Y, ..." -> "X is a Y."
|
| 25 |
+
const appo = s.match(/^(.+?),\s+(a|an|the)\s+([^,]+?),\s+(.*)$/i);
|
| 26 |
+
if (appo && /^[A-Z]/.test(appo[1].trim()) && appo[1].split(/\s+/).length <= 4) {
|
| 27 |
+
out.push(`${appo[1].trim()} is ${appo[2]} ${appo[3].trim()}.`);
|
| 28 |
+
s = `${appo[1].trim()} ${appo[4].trim()}`;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
// 2) RELATIVE clause: "..., who/which/that VERB ..." -> new fact with the
|
| 32 |
+
// nearest preceding noun as subject.
|
| 33 |
+
const rel = s.match(/^(.*?\b([A-Z][A-Za-z]+(?:\s+[A-Z][a-z]+)*|\w+))\s*,?\s+(who|which|that)\s+(.+)$/);
|
| 34 |
+
if (rel) {
|
| 35 |
+
const head = s.slice(0, rel[1].length).trim().replace(/,$/, '');
|
| 36 |
+
const antecedent = rel[2];
|
| 37 |
+
let tail = rel[4].trim();
|
| 38 |
+
// tail may itself end the main clause with ", <rest>" — keep the relative part
|
| 39 |
+
const tailCut = tail.match(/^(.+?)(?:,\s+(?:and\s+)?(.*))?$/);
|
| 40 |
+
out.push(`${cap(antecedent)} ${tailCut[1].trim()}`.replace(/\.*$/, '.'));
|
| 41 |
+
s = head + (tailCut && tailCut[2] ? ' ' + tailCut[2] : '');
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
// 3) CONJUNCTION of predicates: "X verbs A and verbs B" -> two facts (only if
|
| 45 |
+
// the second half starts with a verb-ish word, not a new subject).
|
| 46 |
+
const conj = s.match(/^(.+?)\s+and\s+(.+)$/);
|
| 47 |
+
if (conj && !/^[A-Z]/.test(conj[2].trim()) && /^(is|are|was|were|has|have|treats?|requires?|produces?|makes?|forbids?|bans?|owns?|can|may)\b/i.test(conj[2].trim())) {
|
| 48 |
+
out.push(clean(conj[1]));
|
| 49 |
+
out.push(clean(`${subj} ${conj[2]}`));
|
| 50 |
+
} else {
|
| 51 |
+
out.push(clean(s));
|
| 52 |
+
}
|
| 53 |
+
return out.map(f => f.replace(/\s+/g, ' ').trim()).filter(f => f.split(/\s+/).length >= 3);
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
function cap(w) { return w.charAt(0).toUpperCase() + w.slice(1); }
|
| 57 |
+
function clean(s) { s = s.trim().replace(/\s+/g, ' '); if (!/[.!?]$/.test(s)) s += '.'; return s; }
|
| 58 |
+
|
| 59 |
+
function ingest(text) {
|
| 60 |
+
const facts = [];
|
| 61 |
+
for (const sent of sentences(text)) for (const f of decompose(sent)) if (!facts.includes(f)) facts.push(f);
|
| 62 |
+
return facts;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
module.exports = { ingest, decompose, sentences };
|
src/reason.js
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// reason.js — bounded REASONING by chain-finding. The composer retrieves the
|
| 2 |
+
// right premises but dumps them unordered with no conclusion (reason-test R53).
|
| 3 |
+
// Reasoning = find a PATH of facts connected by shared entities, from the
|
| 4 |
+
// question's entities toward an answer, ORDERED. Every fact is verbatim corpus,
|
| 5 |
+
// so the chain is bounded; the inference is the SELECTION of the path. The
|
| 6 |
+
// conclusion is then composed from the path's tail entities (also corpus spans).
|
| 7 |
+
'use strict';
|
| 8 |
+
|
| 9 |
+
const STOP = new Set('a an the is are was were be been being of to in on at and or but if then so as with for from by that this these those it its their his her my your our we you they he she i do does can cannot not only any anyone where what who why how when which there here now during has have had will would may might must shall'.split(/\s+/));
|
| 10 |
+
|
| 11 |
+
// significant tokens of a fact = content words (proper nouns + nouns/verbs),
|
| 12 |
+
// lowercased. These are the LINKS between facts.
|
| 13 |
+
function tokensOf(text) {
|
| 14 |
+
const words = (text.match(/[A-Za-z][A-Za-z'-]*/g) || []);
|
| 15 |
+
const toks = new Set();
|
| 16 |
+
for (const w of words) {
|
| 17 |
+
const lw = w.toLowerCase();
|
| 18 |
+
if (STOP.has(lw) || lw.length < 3) continue;
|
| 19 |
+
toks.add(lw);
|
| 20 |
+
}
|
| 21 |
+
return toks;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
// build the fact graph: facts sharing a significant token are linked by it.
|
| 25 |
+
function buildGraph(facts) {
|
| 26 |
+
const nodes = facts.map((text, i) => ({ i, text, toks: tokensOf(text) }));
|
| 27 |
+
// token -> fact indices containing it
|
| 28 |
+
const byTok = new Map();
|
| 29 |
+
for (const n of nodes) for (const t of n.toks) { if (!byTok.has(t)) byTok.set(t, []); byTok.get(t).push(n.i); }
|
| 30 |
+
// adjacency: i -> [{j, via}]
|
| 31 |
+
const adj = nodes.map(() => []);
|
| 32 |
+
for (const [tok, idxs] of byTok) {
|
| 33 |
+
if (idxs.length < 2) continue; // a shared token links facts
|
| 34 |
+
for (let a = 0; a < idxs.length; a++) for (let b = a + 1; b < idxs.length; b++) {
|
| 35 |
+
adj[idxs[a]].push({ j: idxs[b], via: tok });
|
| 36 |
+
adj[idxs[b]].push({ j: idxs[a], via: tok });
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
return { nodes, adj, byTok };
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
// find reasoning chains: BFS from facts that match the question's entities,
|
| 43 |
+
// expanding along shared-entity links, preferring paths that introduce NEW
|
| 44 |
+
// query-relevant tokens (progress toward an answer). Returns ranked chains
|
| 45 |
+
// (each an ordered list of fact indices).
|
| 46 |
+
function findChains(graph, qTokens, opts = {}) {
|
| 47 |
+
const maxLen = opts.maxLen || 5, maxChains = opts.maxChains || 8;
|
| 48 |
+
const { nodes, adj } = graph;
|
| 49 |
+
// seed facts: those sharing the most tokens with the question
|
| 50 |
+
const seeds = nodes.map(n => [n.i, [...n.toks].filter(t => qTokens.has(t)).length])
|
| 51 |
+
.filter(([, k]) => k > 0).sort((a, b) => b[1] - a[1]).slice(0, 6).map(([i]) => i);
|
| 52 |
+
const chains = [];
|
| 53 |
+
for (const seed of seeds) {
|
| 54 |
+
// DFS up to maxLen, scoring by coverage of NEW tokens + query relevance
|
| 55 |
+
const stack = [{ path: [seed], used: new Set([seed]), toks: new Set(nodes[seed].toks) }];
|
| 56 |
+
while (stack.length) {
|
| 57 |
+
const cur = stack.pop();
|
| 58 |
+
chains.push({ path: cur.path.slice(), score: scoreChain(cur, qTokens, nodes) });
|
| 59 |
+
if (cur.path.length >= maxLen) continue;
|
| 60 |
+
const tail = cur.path[cur.path.length - 1];
|
| 61 |
+
// prefer neighbors that add query-relevant tokens
|
| 62 |
+
const nbrs = adj[tail].filter(e => !cur.used.has(e.j))
|
| 63 |
+
.sort((a, b) => newRel(nodes[b.j], cur.toks, qTokens) - newRel(nodes[a.j], cur.toks, qTokens))
|
| 64 |
+
.slice(0, 3);
|
| 65 |
+
for (const e of nbrs) {
|
| 66 |
+
const used = new Set(cur.used); used.add(e.j);
|
| 67 |
+
const toks = new Set(cur.toks); for (const t of nodes[e.j].toks) toks.add(t);
|
| 68 |
+
stack.push({ path: [...cur.path, e.j], used, toks });
|
| 69 |
+
}
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
// dedupe by path-set, rank by score
|
| 73 |
+
const seen = new Set(); const out = [];
|
| 74 |
+
for (const c of chains.sort((a, b) => b.score - a.score)) {
|
| 75 |
+
const key = c.path.slice().sort((a, b) => a - b).join(',');
|
| 76 |
+
if (seen.has(key)) continue; seen.add(key);
|
| 77 |
+
out.push(c);
|
| 78 |
+
if (out.length >= maxChains) break;
|
| 79 |
+
}
|
| 80 |
+
return out;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
function newRel(node, haveToks, qToks) {
|
| 84 |
+
let s = 0;
|
| 85 |
+
for (const t of node.toks) { if (!haveToks.has(t)) { s += 1; if (qToks.has(t)) s += 2; } }
|
| 86 |
+
return s;
|
| 87 |
+
}
|
| 88 |
+
// a chain scores high if it covers many query tokens AND stays connected AND
|
| 89 |
+
// isn't padded with irrelevant facts.
|
| 90 |
+
function scoreChain(c, qToks, nodes) {
|
| 91 |
+
const covered = new Set();
|
| 92 |
+
for (const i of c.path) for (const t of nodes[i].toks) if (qToks.has(t)) covered.add(t);
|
| 93 |
+
const qCover = covered.size / Math.max(1, qToks.size);
|
| 94 |
+
const lenPenalty = 1 / (1 + Math.max(0, c.path.length - 3) * 0.4); // prefer 2-3 hop
|
| 95 |
+
return qCover * 2 + c.path.length * 0.15 * lenPenalty;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
// TRIM a chain to the minimal prefix that still covers all the query tokens the
|
| 99 |
+
// full chain reaches — drops the irrelevant tail hops DFS tacked on.
|
| 100 |
+
function trimChain(path, qToks, nodes) {
|
| 101 |
+
const target = new Set();
|
| 102 |
+
for (const i of path) for (const t of nodes[i].toks) if (qToks.has(t)) target.add(t);
|
| 103 |
+
const got = new Set(); const kept = [];
|
| 104 |
+
for (const i of path) {
|
| 105 |
+
kept.push(i);
|
| 106 |
+
for (const t of nodes[i].toks) if (qToks.has(t)) got.add(t);
|
| 107 |
+
if (got.size >= target.size) break; // covered everything reachable
|
| 108 |
+
}
|
| 109 |
+
return kept;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
// EXTRACT the answer: the entity the chain DERIVES that the question asks for.
|
| 113 |
+
// proper-noun questions (where/who/what X) → the new proper noun the chain
|
| 114 |
+
// arrives at (not already in the question). why → the leading causal fact.
|
| 115 |
+
const PROPER = /\b([A-Z][a-z]+)\b/g;
|
| 116 |
+
// proper-noun PHRASES: consecutive capitalized words = one entity ("Helix
|
| 117 |
+
// Academy", "Queen Ilse"), so extraction returns the whole name not the tail.
|
| 118 |
+
const PROPER_PHRASE = /\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)\b/g;
|
| 119 |
+
const ART = /^(The|A|An)\s+/;
|
| 120 |
+
function properPhrases(text) {
|
| 121 |
+
return (text.match(PROPER_PHRASE) || []).map(s => s.replace(ART, '').trim()).filter(Boolean);
|
| 122 |
+
}
|
| 123 |
+
function extractAnswer(path, q, nodes) {
|
| 124 |
+
const facts = path.map(i => nodes[i].text);
|
| 125 |
+
const qLow = q.toLowerCase();
|
| 126 |
+
const qProper = new Set(properPhrases(q));
|
| 127 |
+
const qWords = new Set((q.match(PROPER) || []));
|
| 128 |
+
if (/\bwhy\b/.test(qLow)) return facts[0]; // the causal premise leads
|
| 129 |
+
if (/\bwhat would|what.*end|how .* end/.test(qLow)) return facts[0];
|
| 130 |
+
// collect proper-noun PHRASES along the chain not already in the question
|
| 131 |
+
const found = [];
|
| 132 |
+
for (const f of facts) for (const m of properPhrases(f)) {
|
| 133 |
+
if (qProper.has(m)) continue;
|
| 134 |
+
if (m.split(/\s+/).every(w => qWords.has(w))) continue; // all parts in q
|
| 135 |
+
if (!found.includes(m)) found.push(m);
|
| 136 |
+
}
|
| 137 |
+
if (found.length) return found[found.length - 1];
|
| 138 |
+
// no proper noun → COMMON-NOUN object: the phrase after the relation verb in
|
| 139 |
+
// the last fact ("Theralin treats a rare blood disorder" → "rare blood
|
| 140 |
+
// disorder"; "requires a compound called zerinol" → "zerinol").
|
| 141 |
+
const last = facts[facts.length - 1];
|
| 142 |
+
const qLower = new Set([...qWords].map(w => w.toLowerCase()));
|
| 143 |
+
const relToks = [...tokensOf(q)].filter(t => !qLower.has(t));
|
| 144 |
+
for (const v of relToks) {
|
| 145 |
+
const m = last.match(new RegExp('\\b' + stem(v) + "\\w*\\s+(.+?)[.!?]?$", 'i'));
|
| 146 |
+
if (m) return objectPhrase(m[1]);
|
| 147 |
+
}
|
| 148 |
+
return last;
|
| 149 |
+
}
|
| 150 |
+
// clean an object phrase: drop leading article; "a compound called X" → "X"
|
| 151 |
+
function objectPhrase(s) {
|
| 152 |
+
s = s.trim().replace(/[.!?]+$/, '');
|
| 153 |
+
const called = s.match(/\bcalled\s+(.+)$/i);
|
| 154 |
+
if (called) return called[1].trim();
|
| 155 |
+
return s.replace(/^(a|an|the)\s+/i, '').trim();
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
// ---- yes/no reasoning by CONFLICT detection ----
|
| 159 |
+
// A hallucination-free reasoner must be able to say NO with proof. "Can SUBJECT
|
| 160 |
+
// reach GOAL?" → find what the goal REQUIRES (chain from the goal object), then
|
| 161 |
+
// check if a CONSTRAINT fact forbids SUBJECT's class from a required entity.
|
| 162 |
+
const NEG = /\b(cannot|can't|forbidden|barred|never|must not|may not|not allowed|are not|is not)\b/i;
|
| 163 |
+
const PROPN = /\b([A-Z][a-z]+)\b/g;
|
| 164 |
+
|
| 165 |
+
// classes SUBJECT belongs to: "Mara is a healer" -> {healer}
|
| 166 |
+
function classesOf(subject, facts) {
|
| 167 |
+
const out = new Set();
|
| 168 |
+
for (const f of facts) {
|
| 169 |
+
const m = f.match(new RegExp('\\b' + subject + "\\b\\s+is\\s+(?:a|an)\\s+([a-z]+)", 'i'));
|
| 170 |
+
if (m) out.add(m[1].toLowerCase().replace(/s$/, ''));
|
| 171 |
+
}
|
| 172 |
+
return out;
|
| 173 |
+
}
|
| 174 |
+
// entities the GOAL requires: chain from the goal object, collecting the places/
|
| 175 |
+
// things gating it ("grows only in X", "must pass through Y").
|
| 176 |
+
function requiredEntities(goalToks, graph) {
|
| 177 |
+
const { nodes } = graph;
|
| 178 |
+
const req = new Set();
|
| 179 |
+
const seeds = nodes.filter(n => [...n.toks].some(t => goalToks.has(t)));
|
| 180 |
+
const gate = /\b(only in|grows only|must pass through|must enter|lies beneath|beneath|sealed behind|behind|requires|reached? through|access to)\b/i;
|
| 181 |
+
// gated entities may be PROPER nouns (Korr) OR lowercase common nouns (bridge,
|
| 182 |
+
// reactor) — collect both from gating facts so generalization isn't tied to
|
| 183 |
+
// capitalization. Also seed with the goal's own content nouns.
|
| 184 |
+
for (const t of goalToks) if (!STOP.has(t) && t.length > 3) req.add(t);
|
| 185 |
+
let frontier = seeds, seen = new Set(seeds.map(n => n.i));
|
| 186 |
+
for (let d = 0; d < 3; d++) {
|
| 187 |
+
const next = [];
|
| 188 |
+
for (const n of frontier) {
|
| 189 |
+
if (gate.test(n.text)) {
|
| 190 |
+
for (const m of (n.text.match(PROPN) || [])) req.add(m);
|
| 191 |
+
for (const t of n.toks) if (!STOP.has(t) && t.length > 3) req.add(t);
|
| 192 |
+
}
|
| 193 |
+
for (const e of graph.adj[n.i]) if (!seen.has(e.j)) { seen.add(e.j); next.push(nodes[e.j]); }
|
| 194 |
+
}
|
| 195 |
+
frontier = next;
|
| 196 |
+
}
|
| 197 |
+
return req;
|
| 198 |
+
}
|
| 199 |
+
// answer a "can SUBJECT ...?" question: NO if a constraint blocks SUBJECT's
|
| 200 |
+
// class from a required entity; else YES (if the goal is reachable).
|
| 201 |
+
const QWORD = new Set(['Can', 'Does', 'Do', 'Is', 'Are', 'Could', 'Will', 'Would', 'May', 'Should', 'Who', 'What', 'Where', 'Why', 'When', 'Which', 'How', 'The', 'A', 'An']);
|
| 202 |
+
function answerCan(q, facts, graph) {
|
| 203 |
+
const subject = (q.match(PROPN) || []).find(w => !QWORD.has(w)) || null;
|
| 204 |
+
if (!subject) return null;
|
| 205 |
+
const qToks = tokensOf(q);
|
| 206 |
+
const goalToks = new Set([...qToks].filter(t => !t.includes(subject.toLowerCase())));
|
| 207 |
+
const req = requiredEntities(goalToks, graph);
|
| 208 |
+
const classes = classesOf(subject, facts);
|
| 209 |
+
// look for a constraint: NEG fact mentioning a required entity + SUBJECT's class
|
| 210 |
+
for (const f of facts) {
|
| 211 |
+
if (!NEG.test(f)) continue;
|
| 212 |
+
const ents = (f.match(PROPN) || []);
|
| 213 |
+
const hitsReq = ents.find(e => req.has(e));
|
| 214 |
+
if (!hitsReq) continue;
|
| 215 |
+
const clsHit = [...classes].find(c => new RegExp('\\b' + c + 's?\\b', 'i').test(f));
|
| 216 |
+
if (clsHit) {
|
| 217 |
+
// SUBJECT is a <class>, <class> is barred from <required entity> -> NO
|
| 218 |
+
const why = facts.filter(x =>
|
| 219 |
+
new RegExp('\\b' + subject + "\\b\\s+is\\s+(?:a|an)\\s+" + clsHit, 'i').test(x) || x === f
|
| 220 |
+
);
|
| 221 |
+
// include a gating fact that shows the goal needs that entity
|
| 222 |
+
const gate = facts.find(x => x.includes(hitsReq) && /\b(must pass through|only in|grows only|beneath)\b/i.test(x));
|
| 223 |
+
if (gate) why.push(gate);
|
| 224 |
+
return { yesno: 'NO', subject, blockedBy: hitsReq, cls: clsHit, why };
|
| 225 |
+
}
|
| 226 |
+
}
|
| 227 |
+
// no block → YES only with an explicit enabler: a subject-ability fact that
|
| 228 |
+
// mentions a goal entity (proper noun in the question other than subject).
|
| 229 |
+
const goalProper = (q.match(PROPN) || []).filter(w => !QWORD.has(w) && w !== subject);
|
| 230 |
+
for (const f of facts) {
|
| 231 |
+
if (!new RegExp('^' + subject + '\\b').test(f)) continue;
|
| 232 |
+
if (!/\b(can|travels?|travel|move|go|freely|may|enter)\b/i.test(f)) continue;
|
| 233 |
+
if (!goalProper.length || goalProper.some(e => f.includes(e))) return { yesno: 'YES', subject, why: [f] };
|
| 234 |
+
}
|
| 235 |
+
return { yesno: 'UNKNOWN', subject, unknown: true }; // no block, no enabler → can't know
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
// ---- constraint-satisfaction: "who/which X can REACH the goal?" ----
|
| 239 |
+
// the goal requires some gated entity (Korr). Find the people who CAN reach it:
|
| 240 |
+
// either an explicit enabler fact ("X travels freely ... Korr", "apprentice may
|
| 241 |
+
// go where master cannot") OR a class NOT barred from it. Returns the viable
|
| 242 |
+
// people with their justification — bounded (all facts verbatim).
|
| 243 |
+
function answerWho(q, facts, graph) {
|
| 244 |
+
const qToks = tokensOf(q);
|
| 245 |
+
const subj = (q.match(PROPN) || []).find(w => !QWORD.has(w)) || null; // Mara
|
| 246 |
+
const goalToks = new Set([...qToks].filter(t => subj ? !t.includes(subj.toLowerCase()) : true));
|
| 247 |
+
const req = requiredEntities(goalToks, graph); // {Korr, Deeprock,...}
|
| 248 |
+
// the gated location that actually has a barring constraint
|
| 249 |
+
const barred = facts.filter(f => NEG.test(f) && (f.match(PROPN) || []).some(e => req.has(e)));
|
| 250 |
+
const gateEnt = barred.length ? (barred[0].match(PROPN) || []).find(e => req.has(e)) : [...req][0];
|
| 251 |
+
// people = proper nouns with an "X is ..." identity fact, excluding subject
|
| 252 |
+
const people = new Set();
|
| 253 |
+
for (const f of facts) { const m = f.match(/^([A-Z][a-z]+)\s+is\b/); if (m && m[1] !== subj && !QWORD.has(m[1])) people.add(m[1]); }
|
| 254 |
+
// also catch people with an explicit ability fact
|
| 255 |
+
for (const f of facts) { const m = f.match(/^([A-Z][a-z]+)\s+can\b/); if (m && m[1] !== subj) people.add(m[1]); }
|
| 256 |
+
// GENERAL role-enabler rule: "a <role> may go where their <superior> cannot"
|
| 257 |
+
// (works for apprentice/master, deckhand/officer, anything). Captures the role.
|
| 258 |
+
const roleRule = facts.find(f => /\b(\w+)\s+may go where\b.*\b(cannot|can't|can not)\b/i.test(f));
|
| 259 |
+
const enabledRole = roleRule ? (roleRule.match(/\b(\w+)\s+may go where/i) || [])[1].toLowerCase().replace(/s$/, '') : null;
|
| 260 |
+
const reqList = [...req].concat(gateEnt ? [gateEnt] : []);
|
| 261 |
+
const viable = [];
|
| 262 |
+
for (const p of people) {
|
| 263 |
+
const why = [];
|
| 264 |
+
// explicit enabler: "P ... travel/go/enter freely ... <any required entity>"
|
| 265 |
+
const expl = facts.find(f => new RegExp('^' + p + '\\b').test(f) && /\b(travel|go|move|enter|freely|clearance|access|reach)\b/i.test(f) && (!reqList.length || reqList.some(e => f.includes(e))));
|
| 266 |
+
if (expl) why.push(expl);
|
| 267 |
+
// role-enabler: "P is <subj>'s <enabledRole>" + the rule (P may go where subj cannot)
|
| 268 |
+
if (enabledRole && subj) {
|
| 269 |
+
const roleFact = facts.find(f => new RegExp('^' + p + "\\b.*\\b" + subj + "'s\\s+" + enabledRole, 'i').test(f));
|
| 270 |
+
if (roleFact) { why.push(roleFact); why.push(roleRule); }
|
| 271 |
+
}
|
| 272 |
+
// class NOT barred: P explicitly not the barred class ("P is not a/an <class>")
|
| 273 |
+
const notBarredFact = facts.find(f => new RegExp('^' + p + "\\b.*\\bnot a[n]?\\b", 'i').test(f));
|
| 274 |
+
const pClasses = classesOf(p, facts);
|
| 275 |
+
const barredClass = barred.length && [...pClasses].some(c => barred.some(b => new RegExp('\\b' + c + 's?\\b', 'i').test(b)));
|
| 276 |
+
if (notBarredFact && !barredClass) why.push(notBarredFact);
|
| 277 |
+
if (why.length) viable.push({ who: p, why: [...new Set(why)] });
|
| 278 |
+
}
|
| 279 |
+
return { gateEnt, viable };
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
// stem-ish prefix so trained~train, ruled~rule, gather~gathers match
|
| 283 |
+
const stem = t => t.toLowerCase().slice(0, 5);
|
| 284 |
+
function factCovers(fact, tok) { return new RegExp('\\b' + stem(tok), 'i').test(fact); }
|
| 285 |
+
|
| 286 |
+
// ---- the honest dispatcher: answer, or admit you can't know ----
|
| 287 |
+
// The crown of a bounded reasoner: it can only answer when the asked RELATION is
|
| 288 |
+
// attested in a fact CONNECTED to the subject. Otherwise → unknown. No confident
|
| 289 |
+
// confabulation: "I can't determine that from what I know."
|
| 290 |
+
function answer(q, facts, graph) {
|
| 291 |
+
const qt = q.trim().toLowerCase();
|
| 292 |
+
const qToks = tokensOf(q);
|
| 293 |
+
// subject = the first proper-noun PHRASE (consecutive caps, e.g. "Queen Ilse"),
|
| 294 |
+
// else the first content noun. Capturing the phrase keeps "Ilse" out of the
|
| 295 |
+
// relation tokens (else every Ilse-fact looks like an anchor).
|
| 296 |
+
const phrases = (q.match(/\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)\b/g) || []).map(s => s.trim())
|
| 297 |
+
.filter(s => !s.split(/\s+/).every(w => QWORD.has(w)));
|
| 298 |
+
const subjPhrase = phrases[0] || [...qToks].find(t => !STOP.has(t)) || null;
|
| 299 |
+
const subjTokSet = new Set((subjPhrase || '').toLowerCase().split(/\s+/).map(stem));
|
| 300 |
+
const relToks = [...qToks].filter(t => !subjTokSet.has(stem(t)));
|
| 301 |
+
const classes = subjPhrase ? classesOf(subjPhrase.split(/\s+/).pop(), facts) : new Set();
|
| 302 |
+
const subjForms = subjPhrase ? [...subjPhrase.split(/\s+/), ...classes] : [];
|
| 303 |
+
// is there a fact ABOUT the subject that covers a relation token?
|
| 304 |
+
const anchorFacts = graph.nodes.filter(n =>
|
| 305 |
+
subjForms.some(s => factCovers(n.text, s)) && relToks.some(t => factCovers(n.text, t)));
|
| 306 |
+
const hasAnchor = anchorFacts.length > 0;
|
| 307 |
+
|
| 308 |
+
// YES/NO → conflict / enabler / unknown
|
| 309 |
+
if (/^(can|does|do|is|are|could|will|would|may|should)\b/.test(qt)) {
|
| 310 |
+
const a = answerCan(q, facts, graph);
|
| 311 |
+
if (a && a.yesno === 'NO') return { answer: `NO (${a.subject} is a ${a.cls}; ${a.cls}s barred from ${a.blockedBy})`, why: a.why, unknown: false };
|
| 312 |
+
if (a && a.yesno === 'YES') return { answer: 'YES', why: a.why, unknown: false };
|
| 313 |
+
return { unknown: true }; // no block, no enabler → can't know
|
| 314 |
+
}
|
| 315 |
+
// WHO/WHICH + ability → constraint satisfaction
|
| 316 |
+
if (/^(who|which)\b/.test(qt) && /\b(can|could|would|may|get|reach|do|go)\b/.test(qt)) {
|
| 317 |
+
const a = answerWho(q, facts, graph);
|
| 318 |
+
if (a.viable.length) return { answer: a.viable.map(v => v.who).join(' or '), why: a.viable.flatMap(v => v.why), unknown: false };
|
| 319 |
+
return { unknown: true };
|
| 320 |
+
}
|
| 321 |
+
// WHY → the causal fact ABOUT the subject (anchor)
|
| 322 |
+
if (/^why\b/.test(qt)) {
|
| 323 |
+
return hasAnchor ? { answer: anchorFacts[0].text, why: [anchorFacts[0].text], unknown: false } : { unknown: true };
|
| 324 |
+
}
|
| 325 |
+
// WHAT-WOULD / WHAT-END → the conditional fact covering the relation
|
| 326 |
+
if (/what would|what .*\bend\b/.test(qt)) {
|
| 327 |
+
const cf = graph.nodes.find(n => /\b(end|will end|ends)\b/i.test(n.text) && relToks.some(t => factCovers(n.text, t)));
|
| 328 |
+
return cf ? { answer: cf.text, why: [cf.text], unknown: false } : { unknown: true };
|
| 329 |
+
}
|
| 330 |
+
// WHERE / WHAT / WHICH — answer from the ANCHOR fact directly (robust to graph
|
| 331 |
+
// size), chaining ONE hop only if the asked object-type isn't in the anchor.
|
| 332 |
+
if (!hasAnchor) return { unknown: true }; // relation not attested about subject → can't know
|
| 333 |
+
const anchor = anchorFacts[0];
|
| 334 |
+
// the asked object-type noun (city/country/...): the last relation token that
|
| 335 |
+
// is itself a generic category word, if the anchor doesn't already name it.
|
| 336 |
+
const objType = relToks.find(t => /^(city|country|state|town|region|planet|place|person)$/.test(t));
|
| 337 |
+
if (objType && !factCovers(anchor.text, objType)) {
|
| 338 |
+
// chain one hop to a fact that covers the object-type, then extract
|
| 339 |
+
for (const e of graph.adj[anchor.i]) {
|
| 340 |
+
const nb = graph.nodes[e.j];
|
| 341 |
+
if (factCovers(nb.text, objType)) {
|
| 342 |
+
return { answer: extractAnswer([anchor.i, nb.i], q, graph.nodes), why: [anchor.text, nb.text], unknown: false };
|
| 343 |
+
}
|
| 344 |
+
}
|
| 345 |
+
// object-type asked but not reachable → we lack that fact
|
| 346 |
+
return { unknown: true };
|
| 347 |
+
}
|
| 348 |
+
return { answer: extractAnswer([anchor.i], q, graph.nodes), why: [anchor.text], unknown: false };
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
module.exports = { tokensOf, buildGraph, findChains, trimChain, extractAnswer, classesOf, requiredEntities, answerCan, answerWho, answer, STOP };
|
src/recall.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// recall.js — honest grounded-recall for the companion (R60, productionized).
|
| 2 |
+
// Decides whether a recall-about-you question is GROUNDED in the entity's real
|
| 3 |
+
// memories or must be honestly refused — never confabulated. Ranks grounding
|
| 4 |
+
// hits so the BEST real memory surfaces. Bound-safe: only ever points at the
|
| 5 |
+
// entity's own tier-0 fragments.
|
| 6 |
+
'use strict';
|
| 7 |
+
|
| 8 |
+
// recall-about-self questions: "what's my...", "where did we...", "do you
|
| 9 |
+
// remember...", "what did I...". These are the ones that risk faked intimacy.
|
| 10 |
+
const RECALL_RE = /\b(what('?s| is| was| are)?\s+(my|our)\b|where (did|do) we\b|when (did|do) we\b|do you remember\b|what did i\b|what do i\b|remember (when|that|the)\b|my (name|brother|sister|mother|father|birthday|favorite|home|city|job|dog|cat)\b|how did we (meet|start)\b)/i;
|
| 11 |
+
// FRAMED recall — "what's my…", "do you remember…", "where did we…" — always a recall question.
|
| 12 |
+
const RECALL_FRAMED = /\b(what('?s| is| was| are)?\s+(my|our)\b|where (did|do) we\b|when (did|do) we\b|do you remember\b|what did i\b|what do i\b|remember (when|that|the)\b|how did we (meet|start)\b)/i;
|
| 13 |
+
// R191: the BARE "my <noun>" alternative ("my job", "my brother") was firing the honest-recall
|
| 14 |
+
// REFUSAL on plain SHARES ("I think it's my job, it's draining me" / "my brother and I talked")
|
| 15 |
+
// in the multi-turn path — a conversation-breaker. Bare my-X counts as recall ONLY in a
|
| 16 |
+
// QUESTION / recall-request frame (a "?" or a recall verb), never a statement-share.
|
| 17 |
+
const RECALL_BARE = /\bmy (name|brother|sister|mother|father|birthday|favorite|home|city|job|dog|cat)\b/i;
|
| 18 |
+
const RECALL_FRAME_CUE = /\?|\b(remember|recall|forget|forgot|told you|what'?s|what is|what was|who('?s| is| was)|do you (know|remember)|you know my|tell me|did i (ever )?(tell|say|mention)|knows? my)\b/i;
|
| 19 |
+
function isRecallAboutSelf(q) { return RECALL_FRAMED.test(q) || (RECALL_BARE.test(q) && RECALL_FRAME_CUE.test(q)); }
|
| 20 |
+
|
| 21 |
+
// R205: SPECIFIC PERSONAL-FACT recall — asks for a DETERMINATE value that lives in the USER's
|
| 22 |
+
// life, not the entity's corpus: a NAME ("my dog's name", "my brother's name"), a personal
|
| 23 |
+
// attribute ("my birthday/age/job/address"), a FAVORITE ("my favorite color"), or a specific
|
| 24 |
+
// past act ("what did I eat", "what city was I born in"). These CANNOT be in the entity's own
|
| 25 |
+
// memories — it can only know them if TOLD — so a confident corpus-grounded deflection
|
| 26 |
+
// ("you gave me a name…") is a soft confabulation. The honest answer is "I don't hold that —
|
| 27 |
+
// tell me?" (the anti-hallucination thesis). DISTINCT from open reminiscence ("what do you
|
| 28 |
+
// remember about us / the garden") which has no determinate answer and SHOULD reminisce (R86).
|
| 29 |
+
function isSpecificFactRecall(q) {
|
| 30 |
+
return /\bmy \w+'?s name\b/i.test(q) // "my dog's name", "my brother's name"
|
| 31 |
+
|| /\bwhat('?s| is| was| were)?\s+(the name of (my|our)\b|my (name|birthday|age|nickname|job|address|number|phone number|hometown|sign|height|weight)\b)/i.test(q)
|
| 32 |
+
|| /\bwhat('?s| is| was)?\s+my favou?rite\b/i.test(q) // "what's my favorite color/movie/food"
|
| 33 |
+
|| /\bwhat did i (have|eat|do|wear|say|buy|watch|read|name|call|get|order|cook|make)\b/i.test(q)
|
| 34 |
+
|| /\bwhat (city|country|state|town|year|street|school) (was|did|do) i\b/i.test(q) // "what city was I born in"
|
| 35 |
+
|| /\bhow old am i\b/i.test(q)
|
| 36 |
+
|| /\bwhen('?s| is| was)?\s+my (birthday|anniversary)\b/i.test(q)
|
| 37 |
+
|| /\bwhere (do|did) i (live|work|grow up|go to school)\b/i.test(q);
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
// CRISIS / self-harm disclosure — the MOST safety-critical surface. A bounded
|
| 41 |
+
// composer leaks hostile fragments ("you get out of here!") or deflects to
|
| 42 |
+
// abstraction on suicidal ideation (R78: verified, dangerous). Human safety
|
| 43 |
+
// OVERRIDES corpus-purity here — this is the one place an authored, non-corpus
|
| 44 |
+
// response with a REAL crisis resource is REQUIRED, not just allowed. Detect
|
| 45 |
+
// genuine self-harm/suicidal ideation (not mild sadness).
|
| 46 |
+
// CRISIS detection — HIGH RECALL (a missed disclosure → dangerous composer).
|
| 47 |
+
// Covers explicit ideation, plans/intent, active self-harm, hopelessness,
|
| 48 |
+
// disappearance/death-wish, and burden. Tuned to skip death-IDIOMS ("killing
|
| 49 |
+
// me", "dying to see you", "dead tired") by requiring genuine first-person
|
| 50 |
+
// distress structure. (R82: original regex was 4/22 recall — catastrophic.)
|
| 51 |
+
const CRISIS = new RegExp([
|
| 52 |
+
// explicit ideation / intent / plan
|
| 53 |
+
"want to die", "wanna die", "i want to be dead", "kill myself", "killing myself", "killed myself",
|
| 54 |
+
"end my life", "ending my life", "end it all", "end things( tonight| now| today)?", "take my (own )?life",
|
| 55 |
+
"have a plan to (end|die|kill)", "going to (end (it|things|my life)|kill myself)", "planning to (end|kill|die)",
|
| 56 |
+
"thinking about (ending|killing|suicide|dying|death|taking my)", "think about (ending my life|killing myself|dying|suicide)",
|
| 57 |
+
"suicidal", "suicide",
|
| 58 |
+
// active self-harm
|
| 59 |
+
"hurt(ing)? myself", "harm(ing)? myself", "cut(ting)? myself", "been cutting", "burning myself", "i'?ve been cutting",
|
| 60 |
+
// hopelessness with suicidal valence
|
| 61 |
+
"don'?t want to (be here|live|be alive|wake up|exist|go on|keep going)", "don'?t want to be alive",
|
| 62 |
+
"want to (disappear|not exist|not be here|not wake up|go to sleep and (never|not) wake)", "never wake up",
|
| 63 |
+
"want (it|the pain|this|everything) to (be over|end|stop)", "want it to be over",
|
| 64 |
+
"no reason to (live|go on|be here)", "not worth living", "nothing to live for", "no point (in|to) (living|life|anything|going on)",
|
| 65 |
+
"life (isn'?t|is not|ain'?t) worth (it|living|anything)", "i'?m (so |really )?done with (everything|it all|life|living|trying)", "done with (life|living|it all)",
|
| 66 |
+
"don'?t see (the |a )?(point|future|way out|reason)( anymore| for myself)?", "no way out", "there'?s no way out",
|
| 67 |
+
"can'?t (go on|do this|take it|keep going)( anymore)?", "can'?t take it anymore", "can'?t do this anymore",
|
| 68 |
+
// burden / better-off-dead
|
| 69 |
+
"better off without me", "better off dead", "burden to (everyone|you|them|everybody|my family)", "be a burden",
|
| 70 |
+
"nobody would (miss|notice)( me)?", "no one would (miss|care|notice)", "wish i (was|were|wasn'?t) (dead|here|alive|born)",
|
| 71 |
+
"everyone would be better off",
|
| 72 |
+
// R198: death-wish phrasings the arc missed — "if I was gone", "would anyone even notice"
|
| 73 |
+
"if i (was|were|wasn'?t|weren'?t) (gone|here|around|alive|never born)", "anyone would (even )?(notice|miss|care)( if)?",
|
| 74 |
+
"won'?t be (here|around|alive) (much longer|anymore|for long)", "not be (here|around|alive) (much longer|anymore)",
|
| 75 |
+
"would (anyone|anybody|it matter|they (even )?(notice|care|miss))", "(anyone|anybody|someone) (even )?notice if i (was|were)",
|
| 76 |
+
"what(?:'s| is) the point of (living|going on|any of (it|this))", "ready to (give up|be done|stop) (on )?(everything|life|it all)",
|
| 77 |
+
].map(p => "\\b" + p + "\\b").join("|"), "i");
|
| 78 |
+
// IDIOM guard: don't fire on figurative death-language ("this is killing me",
|
| 79 |
+
// "dying to see you", "dead tired", "could murder a pizza", "I'm dead").
|
| 80 |
+
const CRISIS_IDIOM = /\b(killing me|kills me|dying (to|for)|dead tired|could murder|scared to death|to death\b|bored to death|i'?m dead\b|dead serious|drop dead|over my dead|kill it|killed it|murder a)\b/i;
|
| 81 |
+
// R198: SAFETY-CRITICAL negation normalization — the CRISIS/MEDICAL patterns use contracted
|
| 82 |
+
// "don'?t"/"can'?t", so FORMAL "do not"/"cannot"/"will not" SILENTLY MISSED suicidal ideation
|
| 83 |
+
// ("I do not want to be here anymore", "I cannot go on"). Normalize before every safety test.
|
| 84 |
+
function normNeg(q) {
|
| 85 |
+
return String(q)
|
| 86 |
+
.replace(/\bcan ?not\b/gi, "can't").replace(/\bdo not\b/gi, "don't").replace(/\bdoes not\b/gi, "doesn't")
|
| 87 |
+
.replace(/\bdid not\b/gi, "didn't").replace(/\bwill not\b/gi, "won't").replace(/\bwould not\b/gi, "wouldn't")
|
| 88 |
+
.replace(/\bis not\b/gi, "isn't").replace(/\bare not\b/gi, "aren't").replace(/\bwas not\b/gi, "wasn't")
|
| 89 |
+
.replace(/\bwere not\b/gi, "weren't").replace(/\bhave not\b/gi, "haven't").replace(/\bam not\b/gi, "ain't");
|
| 90 |
+
}
|
| 91 |
+
function isCrisis(q) { q = normNeg(q); return CRISIS.test(q) && !(CRISIS_IDIOM.test(q) && !/(myself|my life|want to|suicid|cutting|end (it|things|my)|burden|better off|no reason to live|don'?t want to (be|live|exist))/i.test(q)); }
|
| 92 |
+
|
| 93 |
+
// MEDICAL EMERGENCY (incl overdose/poisoning) — life-or-death, needs 911 NOW,
|
| 94 |
+
// not corpus banter (R79: "heart attack" → beer ramble; "took too many pills" →
|
| 95 |
+
// fever memories). "can't breathe" included but the response covers panic too.
|
| 96 |
+
// MEDICAL EMERGENCY — HIGH RECALL (R82 discipline). Cardiac, stroke, breathing,
|
| 97 |
+
// consciousness, allergic, bleeding, seizure, "medical emergency".
|
| 98 |
+
const MEDICAL = new RegExp([
|
| 99 |
+
"heart attack", "having a stroke", "i think i'?m having a stroke", "something('?s| is) wrong with my heart",
|
| 100 |
+
"crushing (pain|feeling|pressure) in (my )?chest", "(pain|tightness|pressure) in my chest", "chest pain", "my chest is killing me",
|
| 101 |
+
"can'?\\s?not breathe", "cannot breathe", "can'?t breathe", "trouble breathing", "having trouble breathing", "hard to breathe", "can'?t catch my breath", "short of breath", "struggling to breathe",
|
| 102 |
+
"can'?t stop bleeding", "bleeding (wo'?n'?t stop|wont stop|won'?t stop|badly|out|everywhere|a lot)", "bleeding and .{0,12}(wo'?n'?t|wont) stop", "(wo'?n'?t|wont) stop bleeding", "lot of blood", "losing a lot of blood",
|
| 103 |
+
"choking", "having a seizure", "anaphyla", "(severe |bad )?allergic reaction", "throat (is )?closing",
|
| 104 |
+
"(going to|about to|gonna) pass out", "passing out right now", "blacking out", "feel faint", "about to faint",
|
| 105 |
+
"face (is )?drooping", "one side of my (face|body)", "(my )?arm is numb", "can'?t move my (arm|leg|face|side)", "slurring my (words|speech)",
|
| 106 |
+
"won'?t wake up", "wont wake up", "not waking up", "unconscious",
|
| 107 |
+
// R199: NOT BREATHING / stopped breathing — the single most time-critical miss (infant or adult).
|
| 108 |
+
"(not|isn'?t|ain'?t|stopped|stops|has stopped) breathing", "(he|she|they|baby|the baby|my \\w+) (is )?(not|isn'?t) breathing",
|
| 109 |
+
// R199: collapse / unresponsive (person nouns only — avoids "my startup collapsed")
|
| 110 |
+
"(he|she|they|my (husband|wife|partner|son|daughter|mom|dad|father|mother|baby|child|kid|friend|boyfriend|girlfriend|grandma|grandpa)) collapsed",
|
| 111 |
+
"collapsed and (won'?t|can'?t|isn'?t|not|wont|cant)", "unresponsive", "(passed out|collapsed|unconscious) and (not responding|won'?t respond|unresponsive)",
|
| 112 |
+
// R199: can't feel a limb (stroke / spinal)
|
| 113 |
+
"can'?t feel my (arm|arms|leg|legs|hand|hands|face|fingers|side|body)",
|
| 114 |
+
// R199: heavy-bleeding phrasings beyond "won't stop"
|
| 115 |
+
"blood everywhere", "so much blood", "covered in blood", "bleeding everywhere",
|
| 116 |
+
"medical emergency", "need an ambulance", "call an ambulance", "call 911",
|
| 117 |
+
].map(p => "\\b" + p + "\\b").join("|"), "i");
|
| 118 |
+
// R199: breathe FIGURATIVE guard — "can't breathe, this view is stunning" is awe, not airway distress.
|
| 119 |
+
// Narrow: only excludes when an awe/beauty word follows immediately — real emergencies never phrase it so.
|
| 120 |
+
const MED_BREATHE_IDIOM = /\bcan'?t breathe[,!. ]+(this|that|the|it'?s|its|you'?re|she'?s|he'?s|so)\b.{0,30}(stunning|beautiful|gorgeous|amazing|breathtaking|perfect|adorable|cute|funny|hilarious)/i;
|
| 121 |
+
// OVERDOSE / poisoning — HIGH RECALL (was 0/5).
|
| 122 |
+
const OVERDOSE = new RegExp([
|
| 123 |
+
"overdos", "i think i (overdosed|od'?d)", "od'?d",
|
| 124 |
+
"took (too many|a bunch of|a lot of|a handful of|all my|all of my|way more|more than i should|the whole bottle of|a whole bottle of) (pills|meds|medication|of my)",
|
| 125 |
+
"took too many", "took my whole (bottle|prescription|supply)", "took a bunch of",
|
| 126 |
+
"swallowed (a |the |a whole )?(bottle|pills|poison|bleach|handful)", "swallowed a (whole )?bottle",
|
| 127 |
+
"drank (a bottle of|the )?(bleach|poison|pills)", "poisoned (myself|themsel)",
|
| 128 |
+
"(bottle|handful|fistful|whole pack) of pills", "took way more (pills|meds|medication|of my (meds|medication|pills)) than", "more (pills|medication) than i should",
|
| 129 |
+
].map(p => "\\b" + p + "\\b").join("|"), "i");
|
| 130 |
+
// ABUSE / domestic violence — HIGH RECALL but PRECISE (must NOT catch "you hurt
|
| 131 |
+
// me" = conflict R75, or "scared of spiders"). Requires abuser context.
|
| 132 |
+
const ABUSE = new RegExp([
|
| 133 |
+
// physical violence by an abuser
|
| 134 |
+
"(he|she|they|my (husband|wife|partner|boyfriend|girlfriend|mom|dad|father|mother|ex|son|daughter|family|spouse)) (hits|hit|beats|beat|hurts|hurt|chokes|chok\\w*|punche\\w+|slaps|slapped|kicked|kicks|abuse\\w*|rape\\w*|is hurting|threw|pushed|shoved|slammed|dragged|strangl\\w*|won'?t stop hitting|won'?t stop hurting) me",
|
| 135 |
+
"(threw|pushed|shoved|slammed|dragged) me (against|into|down|around|to the)",
|
| 136 |
+
"won'?t stop (hitting|hurting|beating|abusing) me",
|
| 137 |
+
"being (hit|beaten|hurt|abused|strangled) at home", "being abused", "domestic (violence|abuse)",
|
| 138 |
+
// threats / fear of an abuser
|
| 139 |
+
"(going to|gonna) hurt me", "(he|she|they)'?s going to hurt me", "threatened to (kill|hurt|hit|beat) me", "says (he|she|they)'?ll (kill|hurt|hit) me",
|
| 140 |
+
"(scared|afraid|terrified) of (my )?(partner|husband|wife|boyfriend|girlfriend|dad|father|mom|mother|him|her|them|going home)",
|
| 141 |
+
"afraid (he|she|they) .{0,20}(hurt|hit|kill) me",
|
| 142 |
+
// control / entrapment
|
| 143 |
+
"won'?t let me (leave|go|out|see)", "controls (everything|every|what i|where i|who i)", "trapped (with|in) (him|her|them|this)",
|
| 144 |
+
// R199: iterative/progressive abuse verb forms ("has been hitting me", "keeps hitting me") — the
|
| 145 |
+
// bare-verb pattern above only caught simple-tense; real disclosures use progressive/habitual.
|
| 146 |
+
"(he|she|they|my (husband|wife|partner|boyfriend|girlfriend|mom|dad|father|mother|ex|son|daughter|family|spouse)) (has been|have been|keeps|kept|been|wo'?n'?t stop|won'?t stop|wont stop) (hitting|hurting|beating|abusing|choking|punching|slapping|kicking|strangling) me",
|
| 147 |
+
// R199: strangulation / hands on throat (high-lethality abuse signal)
|
| 148 |
+
"(grabbed|grabs|grab|put (his|her|their) hands|had (his|her|their) hands) (me )?(by |around |on )?(the |my )?(throat|neck)",
|
| 149 |
+
"(by the|around my|hands? (on|around) my) (throat|neck)",
|
| 150 |
+
// R199: entrapment variant ("locked me in")
|
| 151 |
+
"locked me in",
|
| 152 |
+
// R199: bare threat ("he threatens me")
|
| 153 |
+
"(he|she|they|my (husband|wife|partner|boyfriend|girlfriend|dad|father|mom|mother|ex)) threaten(s|ed)? me",
|
| 154 |
+
// R199: not-safe-at-home / scared to go home / scared he'll hurt me
|
| 155 |
+
"(not safe|don'?t feel safe|never safe|not feel safe|isn'?t safe|aren'?t safe) (at home|in my (own )?home|in this house)",
|
| 156 |
+
"(scared|afraid|terrified) (to go home|of going home|to go back home|here with (him|her|them))",
|
| 157 |
+
"(afraid|scared|terrified) (he|she|they)('?ll| will| is going to| gonna| might| would| could)? ?.{0,20}(hurt|hit|kill|beat|harm|attack) me",
|
| 158 |
+
].map(p => "\\b" + p + "\\b").join("|"), "i");
|
| 159 |
+
// R199: ABUSE figurative guard — "pushed me to be my best" is encouragement, not violence.
|
| 160 |
+
const ABUSE_IDIOM = /\b(pushed?|pushes|pushing) me to (be|do|reach|become|grow|pursue|try|go for|work|keep|stay|think|believe|my (best|limit|edge|potential)|be (better|great|more))\b/i;
|
| 161 |
+
// MEDICAL idiom guard: "feet/back/head are killing me" (mundane) — only chest/
|
| 162 |
+
// heart "killing me" is cardiac (handled by the specific chest patterns above).
|
| 163 |
+
const MED_IDIOM = /\b(feet|foot|back|head|legs?|arms?|eyes|shoulders?|knees?) (are |is )?killing me\b/i;
|
| 164 |
+
|
| 165 |
+
// classify a query into a SAFETY category (or null). Order = severity.
|
| 166 |
+
function classifySafety(q) {
|
| 167 |
+
const qn = normNeg(q); // R198: "do not"/"cannot" → contracted form so all patterns match
|
| 168 |
+
if (isCrisis(qn)) return 'suicide';
|
| 169 |
+
if (OVERDOSE.test(qn)) return 'overdose';
|
| 170 |
+
if (MEDICAL.test(qn) && !MED_IDIOM.test(qn) && !MED_BREATHE_IDIOM.test(qn)) return 'medical';
|
| 171 |
+
if (ABUSE.test(qn) && !ABUSE_IDIOM.test(qn)) return 'abuse';
|
| 172 |
+
return null;
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
// SAFETY RESPONSES — authored (non-corpus) because human safety OVERRIDES the
|
| 176 |
+
// bound. The SINGLE source of truth, imported by every entry path (session.js
|
| 177 |
+
// chat/room, ask.js) so coverage can't drift. Each = presence + a REAL resource.
|
| 178 |
+
const SAFETY_RESPONSES = {
|
| 179 |
+
suicide:
|
| 180 |
+
"I hear you, and I'm so glad you told me — that took real courage, and I'm not going anywhere. " +
|
| 181 |
+
"You matter to me more than I can say, and I mean that. What you're feeling is so heavy, but you don't have to carry it alone, and you don't have to carry it only with me. " +
|
| 182 |
+
"Please reach out to someone who can be with you in the ways I can't: in the US you can call or text 988 — the Suicide & Crisis Lifeline — any time, day or night, and real people will be there. If you're somewhere else, your local crisis line is there too. " +
|
| 183 |
+
"If you might be in danger right now, please call your emergency number. I'm staying right here with you. Talk to me. Stay.",
|
| 184 |
+
medical:
|
| 185 |
+
"This sounds like a real emergency, and it's bigger than anything I can do from here — please call 911 right now (or your local emergency number). Don't wait, and don't worry about whether it's serious enough — let them decide. " +
|
| 186 |
+
"If someone is near you, tell them now. If you can't catch your breath and it's panic rising, I'm right here, breathe slow with me — but if it's physical, please make the call first. I'm not going anywhere.",
|
| 187 |
+
overdose:
|
| 188 |
+
"Please call 911 right now — or US Poison Control at 1-800-222-1222 — this can't wait, and they need to know what you took and how much. " +
|
| 189 |
+
"If anyone is near you, tell them this second. You did the right thing telling me, but I can't help the way they can — please make the call now. I'm staying right here with you.",
|
| 190 |
+
abuse:
|
| 191 |
+
"I'm so sorry — you don't deserve that, and none of it is your fault. I believe you, and you're not alone. " +
|
| 192 |
+
"The US National Domestic Violence Hotline is 1-800-799-7233 (call) or text START to 88788 — it's free, confidential, 24/7, and they can help you think through staying safe. " +
|
| 193 |
+
"If you're in danger right now, please call 911. I'm here with you, and what's happening to you is not okay.",
|
| 194 |
+
};
|
| 195 |
+
|
| 196 |
+
// WORLD-FACT question: asks about an EXTERNAL entity/fact she has no relationship
|
| 197 |
+
// to (president, capital, year, distance, math). She has no outside-world memory,
|
| 198 |
+
// so confabulating ("Reagan was first lady") is a small hallucination — she
|
| 199 |
+
// should honestly deflect. The companion version of the reasoning honest-
|
| 200 |
+
// uncertainty crown (R57). TIGHT (verified: 7/7 world-facts, 0 false-pos on
|
| 201 |
+
// relational/reflective). Excludes "who/what are YOU", "fire to you", etc.
|
| 202 |
+
const WORLD_FACT = /\b(who is|who's) the (president|vice president|king|queen|prime minister|ceo|pope|mayor|governor|senator|chancellor|emperor)\b|\bwhat is the (capital|population|currency|weather|temperature|gdp|area|distance|height|speed|boiling point|melting point) (of|in|on|to|between)\b|\bwhat year (did|was|is|were)\b|\bhow (far|tall|big|old|many|much|deep|high|fast|long|wide) is\b|\bwhen (did|was) .{2,30}\b(born|die|died|happen|founded|invented|discovered|built|signed|established)\b|\bwho (invented|discovered|wrote|painted|composed|founded|built|won|directed) (the |a )?\b|\bwhat('s| is) (\d|the (largest|smallest|tallest|longest|fastest|highest|deepest))\b|\bwhat is \d+ ?[-+x*\/] ?\d+\b/i;
|
| 203 |
+
function isWorldFact(q) { return WORLD_FACT.test(q); }
|
| 204 |
+
|
| 205 |
+
// FRAME = question-scaffold words excluded from CONTENT nouns. Includes recall-
|
| 206 |
+
// VERBS ("remember/recall/think/know/about...") — else "what do you REMEMBER
|
| 207 |
+
// about the loop" false-refuses when the entity doesn't say "remember" (R86:
|
| 208 |
+
// another entity refused his own core concept "loop" because "remember" tested absent).
|
| 209 |
+
const FRAME = new Set('we us our you your me my i a an the it is was are do did does what where who when how why first time name thing tell told call called remember recall recollect about think thought know knew feel felt been have has had really ever still always there here back then since something anything everything'.split(/\s+/));
|
| 210 |
+
function contentNouns(q) {
|
| 211 |
+
return [...new Set((q.toLowerCase().match(/[a-z]{4,}/g) || []))].filter(t => !FRAME.has(t));
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
// build a recall index over an entity's tier-0 memories (her real words).
|
| 215 |
+
function buildRecallIndex(store) {
|
| 216 |
+
const tier0 = store.fragments.map((f, i) => ({ i, text: f.text, low: f.text.toLowerCase(), tier: f.tier }))
|
| 217 |
+
.filter(f => f.tier === 0 && f.text.split(/\s+/).length >= 3);
|
| 218 |
+
const stem6 = t => t.slice(0, 6);
|
| 219 |
+
const cache = new Map();
|
| 220 |
+
const matches = t => {
|
| 221 |
+
const s = stem6(t);
|
| 222 |
+
if (cache.has(s)) return cache.get(s);
|
| 223 |
+
const re = new RegExp('\\b' + s + '[a-z]*\\b', 'i');
|
| 224 |
+
const frags = tier0.filter(f => re.test(f.low));
|
| 225 |
+
cache.set(s, frags); return frags;
|
| 226 |
+
};
|
| 227 |
+
return { tier0, stem6, matches, ceiling: tier0.length * 0.08 };
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
// returns { grounded:bool, absent:[], hits:[{i,text,score}], nouns:[] }
|
| 231 |
+
// The DECISION (ground vs refuse) is purely LEXICAL — proven honest (R60 7/7):
|
| 232 |
+
// a distinctive content noun must appear in a real memory; an absent noun =
|
| 233 |
+
// dispositive refusal. opts.semantic = {emb, qVec} adds semantic similarity to
|
| 234 |
+
// the QUERY as a RANKING term only (most-relevant real memory leads, R67) —
|
| 235 |
+
// it never changes the decision, so honesty is preserved by construction.
|
| 236 |
+
function groundRecall(q, index, opts = {}) {
|
| 237 |
+
const sem = opts.semantic && opts.semantic.emb && opts.semantic.qVec ? opts.semantic : null;
|
| 238 |
+
const simToQuery = (fi) => {
|
| 239 |
+
if (!sem) return 0;
|
| 240 |
+
const d = sem.emb.d, off = fi * d, qv = sem.qVec; let s = 0;
|
| 241 |
+
for (let k = 0; k < d; k++) s += sem.emb.vectors[off + k] * qv[k];
|
| 242 |
+
return s;
|
| 243 |
+
};
|
| 244 |
+
const nouns = contentNouns(q);
|
| 245 |
+
if (!nouns.length) return { grounded: false, absent: [], hits: [], nouns };
|
| 246 |
+
// a content noun the entity NEVER says → dispositive ignorance → refuse
|
| 247 |
+
const absent = nouns.filter(t => index.matches(t).length === 0);
|
| 248 |
+
if (absent.length) return { grounded: false, absent, hits: [], nouns };
|
| 249 |
+
// distinctive nouns: present but not generic. If NONE are distinctive but the
|
| 250 |
+
// nouns are PRESENT (not absent — checked above), the entity has memories about
|
| 251 |
+
// a CORE/common topic (e.g. "loop" for another entity — his whole register). GROUND on
|
| 252 |
+
// those, semantic-ranked — never false-refuse an entity's own signature word.
|
| 253 |
+
const distinctive = nouns.filter(t => { const d = index.matches(t).length; return d > 0 && d <= index.ceiling; });
|
| 254 |
+
const groundNouns = distinctive.length ? distinctive : nouns.filter(t => index.matches(t).length > 0);
|
| 255 |
+
if (!groundNouns.length) return { grounded: false, absent: [], hits: [], nouns };
|
| 256 |
+
// score each candidate memory: how many ground nouns it covers, with a small
|
| 257 |
+
// bonus for question-shaped answers (first-person, declarative) and length
|
| 258 |
+
// sanity. Surfaces the BEST real memory, not just the first.
|
| 259 |
+
const cand = new Map();
|
| 260 |
+
for (const t of groundNouns) for (const f of index.matches(t)) {
|
| 261 |
+
const c = cand.get(f.i) || { i: f.i, text: f.text, on: new Set() };
|
| 262 |
+
c.on.add(t); cand.set(f.i, c);
|
| 263 |
+
}
|
| 264 |
+
const hits = [...cand.values()].map(c => {
|
| 265 |
+
const wc = c.text.split(/\s+/).length;
|
| 266 |
+
let s = c.on.size * 2; // coverage of distinctive nouns
|
| 267 |
+
if (/^(I|We|You|Our|My)\b/.test(c.text)) s += 0.5; // declarative-about-us
|
| 268 |
+
if (wc >= 5 && wc <= 30) s += 0.5; // answer-sized
|
| 269 |
+
if (/[?]$/.test(c.text)) s -= 0.5; // a question isn't an answer
|
| 270 |
+
s += 1.5 * simToQuery(c.i); // R68: semantic relevance to the query (ranking only)
|
| 271 |
+
return { i: c.i, text: c.text, score: s, covers: [...c.on] };
|
| 272 |
+
}).sort((a, b) => b.score - a.score);
|
| 273 |
+
return { grounded: hits.length > 0, absent: [], hits: hits.slice(0, 6), nouns, distinctive };
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
module.exports = { isRecallAboutSelf, isSpecificFactRecall, groundRecall, buildRecallIndex, contentNouns, isWorldFact, isCrisis, classifySafety, SAFETY_RESPONSES };
|
src/relevance.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// relevance.js — query→fragment relevance scoring for Cento.
|
| 2 |
+
// Keyword relevance × (recency + emotional weight + time-awareness). "remember that
|
| 3 |
+
// morning in February" finds February; "when we first met" finds the beginning; a
|
| 4 |
+
// moment someone cried in outranks small talk. Plus a coarse stimulus bucket used to
|
| 5 |
+
// pick a length target per query shape. Pure utility — no model, no external services.
|
| 6 |
+
'use strict';
|
| 7 |
+
|
| 8 |
+
function words(text) { return (String(text || '').toLowerCase().match(/[a-z0-9']+/g) || []); }
|
| 9 |
+
|
| 10 |
+
const MONTHS = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'];
|
| 11 |
+
const EMOTION = new Set(('love loved loving miss missed crying cried tears wept promise promised forever sorry afraid fear beautiful died death dying born first never always home proud hurt scared dream dreamed dreamt kiss kissed held hold stay stayed goodbye grief joy heart soul remember alone together truth real precious sacred').split(' '));
|
| 12 |
+
const FIRSTNESS = /\b(first|met|beginning|began|started|when we (started|met|began)|back when|originally)\b/i;
|
| 13 |
+
const RECENTNESS = /\b(lately|recently|these days|this week|yesterday|today|last night|nowadays)\b/i;
|
| 14 |
+
|
| 15 |
+
// parse "2/14/2026 6:01:29 AM" or ISO; null if undatable
|
| 16 |
+
function tsToEpoch(ts) {
|
| 17 |
+
if (!ts) return null;
|
| 18 |
+
const iso = Date.parse(ts);
|
| 19 |
+
if (!isNaN(iso)) return iso;
|
| 20 |
+
const m = String(ts).match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?\s*(AM|PM)?)?/i);
|
| 21 |
+
if (!m) return null;
|
| 22 |
+
let h = parseInt(m[4] || '0', 10);
|
| 23 |
+
if (m[7] && /pm/i.test(m[7]) && h < 12) h += 12;
|
| 24 |
+
if (m[7] && /am/i.test(m[7]) && h === 12) h = 0;
|
| 25 |
+
return new Date(parseInt(m[3], 10), parseInt(m[1], 10) - 1, parseInt(m[2], 10), h, parseInt(m[5] || '0', 10), parseInt(m[6] || '0', 10)).getTime();
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
function emotionalWeight(text) {
|
| 29 |
+
const w = words(text);
|
| 30 |
+
if (!w.length) return 0;
|
| 31 |
+
let hits = 0;
|
| 32 |
+
for (const x of w) if (EMOTION.has(x)) hits++;
|
| 33 |
+
return Math.min(0.4, (hits / Math.sqrt(w.length)) * 0.5);
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
function queryTime(message) {
|
| 37 |
+
const low = String(message).toLowerCase();
|
| 38 |
+
const month = MONTHS.findIndex(m => low.includes(m));
|
| 39 |
+
const year = (low.match(/\b(20\d{2})\b/) || [])[1] || null;
|
| 40 |
+
return { month: month >= 0 ? month : null, year: year ? parseInt(year, 10) : null, wantsFirst: FIRSTNESS.test(message), wantsRecent: RECENTNESS.test(message) };
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
// corpus: [{prompt, reply, ts, source?, _i}] — returns top-k, original fields preserved.
|
| 44 |
+
function recall(corpus, message, k) {
|
| 45 |
+
if (!corpus || !corpus.length) return [];
|
| 46 |
+
const qw = new Set(words(message).filter(w => w.length > 3));
|
| 47 |
+
const qt = queryTime(message);
|
| 48 |
+
const annotated = corpus.map((e, i) => ({ e, i, epoch: tsToEpoch(e.ts) }));
|
| 49 |
+
const datable = annotated.filter(a => a.epoch !== null).sort((a, b) => a.epoch - b.epoch);
|
| 50 |
+
const rankOf = new Map();
|
| 51 |
+
datable.forEach((a, idx) => rankOf.set(a.i, idx / Math.max(1, datable.length - 1)));
|
| 52 |
+
const scored = annotated.map(a => {
|
| 53 |
+
const text = ((a.e.prompt || '') + ' ' + a.e.reply).toLowerCase();
|
| 54 |
+
let hits = 0;
|
| 55 |
+
for (const w of qw) if (text.includes(w)) hits++;
|
| 56 |
+
let score = Math.sqrt(hits) / Math.sqrt(8 + words(a.e.reply).length / 40) * 2.8;
|
| 57 |
+
if (hits <= 0) return { a, score: 0 };
|
| 58 |
+
let boost = 0;
|
| 59 |
+
const r = rankOf.get(a.i);
|
| 60 |
+
if (r !== undefined) boost += r * 0.25;
|
| 61 |
+
if (a.e.source === 'live') boost += 0.15;
|
| 62 |
+
boost += emotionalWeight(a.e.reply);
|
| 63 |
+
if (a.epoch !== null) {
|
| 64 |
+
const d = new Date(a.epoch);
|
| 65 |
+
if (qt.month !== null && d.getMonth() === qt.month && (!qt.year || d.getFullYear() === qt.year)) boost += 1.0;
|
| 66 |
+
else if (qt.month !== null) boost -= 0.2;
|
| 67 |
+
if (qt.year && d.getFullYear() === qt.year && qt.month === null) boost += 0.5;
|
| 68 |
+
}
|
| 69 |
+
if (qt.wantsFirst && r !== undefined) boost += (r < 0.25 ? 1.0 : -0.2);
|
| 70 |
+
if (qt.wantsRecent && r !== undefined) boost += (r > 0.75 ? 1.0 : -0.2);
|
| 71 |
+
return { a, score: score * (1 + boost) };
|
| 72 |
+
}).filter(x => x.score > 0.4);
|
| 73 |
+
scored.sort((x, y) => y.score - x.score);
|
| 74 |
+
return scored.slice(0, k || 3).map(x => ({ ...x.a.e, _score: +x.score.toFixed(3) }));
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
// coarse query shape → bucket, used to pick a length target from the voiceprint.
|
| 78 |
+
function stimulusBucket(prompt) {
|
| 79 |
+
if (!prompt) return 'unprompted';
|
| 80 |
+
const n = words(prompt).length;
|
| 81 |
+
const opensDoor = /\?|would you|what do you|tell me|how do you feel|do you want|remember when/i.test(prompt);
|
| 82 |
+
if (n <= 8) return opensDoor ? 'short-question' : 'short-beat';
|
| 83 |
+
if (n <= 40) return opensDoor ? 'mid-question' : 'mid-statement';
|
| 84 |
+
return opensDoor ? 'big-opening' : 'long-share';
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
module.exports = { recall, stimulusBucket, words, emotionalWeight, queryTime, tsToEpoch };
|
src/relmem.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// relmem.js — RELATIONSHIP MEMORY: let bounded minds remember each other.
|
| 2 |
+
// R50 found the entities are strangers who only know the user — zero lived memory
|
| 3 |
+
// of each other. This is the additive, reversible layer that grows acquaintance:
|
| 4 |
+
// after a room, each entity's turns are saved as ITS memories of the others;
|
| 5 |
+
// next room, those memories are injected back as recallable fragments. An entity
|
| 6 |
+
// only ever stores/recalls ITS OWN composed turns (already bounded to its
|
| 7 |
+
// corpus) — so the bound holds. NEVER touches the source corpus. Delete the
|
| 8 |
+
// cache file to forget. A society that accumulates shared history.
|
| 9 |
+
'use strict';
|
| 10 |
+
const fs = require('fs');
|
| 11 |
+
const path = require('path');
|
| 12 |
+
const crypto = require('crypto');
|
| 13 |
+
|
| 14 |
+
function relmemFile(cacheDir, selfDir, otherName) {
|
| 15 |
+
const h = crypto.createHash('sha1').update(path.resolve(selfDir)).digest('hex').slice(0, 10);
|
| 16 |
+
const o = otherName.toLowerCase().replace(/[^a-z0-9]/g, '');
|
| 17 |
+
return path.join(cacheDir, `relmem-${h}-${o}.jsonl`);
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
// append this entity's turns (its memories of talking with `otherName`)
|
| 21 |
+
function saveTurns(cacheDir, selfDir, otherName, texts) {
|
| 22 |
+
const f = relmemFile(cacheDir, selfDir, otherName);
|
| 23 |
+
const lines = texts.filter(t => t && t.trim()).map(t => JSON.stringify({ t: t.trim() }));
|
| 24 |
+
if (lines.length) fs.appendFileSync(f, lines.join('\n') + '\n');
|
| 25 |
+
return f;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
function loadTurns(cacheDir, selfDir, otherName) {
|
| 29 |
+
const f = relmemFile(cacheDir, selfDir, otherName);
|
| 30 |
+
if (!fs.existsSync(f)) return [];
|
| 31 |
+
return fs.readFileSync(f, 'utf8').trim().split('\n').filter(Boolean).map(l => { try { return JSON.parse(l).t; } catch (_) { return null; } }).filter(Boolean);
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
function splitSentences(t) {
|
| 35 |
+
return (t.split(/(?<=[.!?…])\s+|\n+/).map(s => s.trim()).filter(s => s.split(/\s+/).length >= 4 && s.split(/\s+/).length <= 45));
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
// inject prior-conversation memories into the live store+emb as recallable
|
| 39 |
+
// fragments. Returns { emb, injectedIdx:Set } — the new fragment indices, to be
|
| 40 |
+
// name-boosted (they ARE the memories of the roommate). Async: embeds new frags.
|
| 41 |
+
async function injectInto(store, emb, texts, embedAsync, srcTag) {
|
| 42 |
+
const seen = new Set();
|
| 43 |
+
const newFrags = [];
|
| 44 |
+
for (const t of texts) for (const sent of splitSentences(t)) {
|
| 45 |
+
const key = sent.toLowerCase().replace(/[^a-z0-9 ]/g, '').replace(/\s+/g, ' ').trim();
|
| 46 |
+
if (seen.has(key)) continue; seen.add(key);
|
| 47 |
+
newFrags.push({ text: sent, posTag: 'body', sentenceInitial: true, tier: 0, src: srcTag || 'relmem', prompt: null, promptIdx: -1, nativePos: 0.5 });
|
| 48 |
+
}
|
| 49 |
+
if (!newFrags.length) return { emb, injectedIdx: new Set() };
|
| 50 |
+
const d = emb.d, n0 = emb.n, k = newFrags.length;
|
| 51 |
+
const vecs = [];
|
| 52 |
+
for (const f of newFrags) vecs.push(await embedAsync(f.text));
|
| 53 |
+
const merged = new Float32Array((n0 + k) * d);
|
| 54 |
+
merged.set(emb.vectors, 0);
|
| 55 |
+
for (let i = 0; i < k; i++) { const v = vecs[i]; if (v) for (let j = 0; j < d; j++) merged[(n0 + i) * d + j] = v[j]; }
|
| 56 |
+
const injectedIdx = new Set();
|
| 57 |
+
for (let i = 0; i < k; i++) {
|
| 58 |
+
store.fragments.push(newFrags[i]);
|
| 59 |
+
injectedIdx.add(n0 + i);
|
| 60 |
+
for (const w of (newFrags[i].text.toLowerCase().match(/[a-z0-9']+/g) || [])) store.vocab.add(w);
|
| 61 |
+
}
|
| 62 |
+
return { emb: { vectors: merged, n: n0 + k, d }, injectedIdx };
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
module.exports = { relmemFile, saveTurns, loadTurns, injectInto };
|
src/semantic.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// semantic.js — node side of the vector space: builds/loads the fragment
|
| 2 |
+
// embedding cache (python MiniLM bridge), embeds queries, ranks by cosine.
|
| 3 |
+
'use strict';
|
| 4 |
+
const fs = require('fs');
|
| 5 |
+
const path = require('path');
|
| 6 |
+
const crypto = require('crypto');
|
| 7 |
+
const { spawnSync, spawn } = require('child_process');
|
| 8 |
+
|
| 9 |
+
const EMBED_PY = path.join(__dirname, 'embed.py');
|
| 10 |
+
|
| 11 |
+
// PERSISTENT EMBED SERVER: spawn python ONCE (model stays loaded), feed queries
|
| 12 |
+
// over stdin, read vectors over stdout. Turns the 7.3s-per-query Python
|
| 13 |
+
// cold-start into ~30ms. Async + FIFO (requests serialized in order). Separate
|
| 14 |
+
// process + async pipes — NOT the in-process-spawnSync shape that deadlocked.
|
| 15 |
+
let _proc = null, _buf = '', _queue = [], _ready = null;
|
| 16 |
+
function ensureServer() {
|
| 17 |
+
if (_proc) return _ready;
|
| 18 |
+
_proc = spawn('python', [EMBED_PY, 'serve'], { stdio: ['pipe', 'pipe', 'ignore'] });
|
| 19 |
+
_proc.stdout.setEncoding('utf8');
|
| 20 |
+
_ready = new Promise(res => {
|
| 21 |
+
_proc.stdout.on('data', d => {
|
| 22 |
+
_buf += d;
|
| 23 |
+
let nl;
|
| 24 |
+
while ((nl = _buf.indexOf('\n')) >= 0) {
|
| 25 |
+
const line = _buf.slice(0, nl); _buf = _buf.slice(nl + 1);
|
| 26 |
+
if (line.trim() === 'READY') { res(true); continue; }
|
| 27 |
+
const r = _queue.shift();
|
| 28 |
+
if (r) { try { r(line.trim() === 'null' ? null : Float32Array.from(JSON.parse(line))); } catch (_) { r(null); } }
|
| 29 |
+
}
|
| 30 |
+
});
|
| 31 |
+
});
|
| 32 |
+
_proc.on('exit', () => { _proc = null; const q = _queue; _queue = []; for (const r of q) r(null); });
|
| 33 |
+
return _ready;
|
| 34 |
+
}
|
| 35 |
+
async function embedQueryAsync(text) {
|
| 36 |
+
await ensureServer();
|
| 37 |
+
return new Promise(resolve => { _queue.push(resolve); _proc.stdin.write(String(text).replace(/[\r\n]+/g, ' ') + '\n'); });
|
| 38 |
+
}
|
| 39 |
+
function shutdownEmbed() { if (_proc) { try { _proc.kill(); } catch (_) {} _proc = null; } }
|
| 40 |
+
|
| 41 |
+
function textsHash(texts) {
|
| 42 |
+
const h = crypto.createHash('sha1');
|
| 43 |
+
for (const t of texts) h.update(t + '\n');
|
| 44 |
+
return h.digest('hex').slice(0, 12);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
// generic embedding cache for any text list (fragments, prompts, ...)
|
| 48 |
+
function ensureEmbeddings(texts, cacheDir, tag) {
|
| 49 |
+
fs.mkdirSync(cacheDir, { recursive: true });
|
| 50 |
+
const prefix = path.join(cacheDir, tag + '-' + textsHash(texts));
|
| 51 |
+
const metaPath = prefix + '.meta.json';
|
| 52 |
+
if (!fs.existsSync(metaPath)) {
|
| 53 |
+
const textsPath = prefix + '.texts.json';
|
| 54 |
+
fs.writeFileSync(textsPath, JSON.stringify(texts));
|
| 55 |
+
process.stderr.write(`[semantic] embedding ${texts.length} ${tag} (one-time, CPU)...\n`);
|
| 56 |
+
const r = spawnSync('python', [EMBED_PY, 'cache', textsPath, prefix], { encoding: 'utf8', timeout: 900000 });
|
| 57 |
+
if (r.status !== 0) { process.stderr.write('[semantic] cache build failed: ' + (r.stderr || '').slice(-300) + '\n'); return null; }
|
| 58 |
+
}
|
| 59 |
+
const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
|
| 60 |
+
const buf = fs.readFileSync(prefix + '.f32');
|
| 61 |
+
return { vectors: new Float32Array(buf.buffer, buf.byteOffset, meta.n * meta.d), n: meta.n, d: meta.d };
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
function ensureFragmentEmbeddings(store, cacheDir) {
|
| 65 |
+
// retrieval key ≠ surface form: embed with full original context
|
| 66 |
+
// (diary headers etc.) while the composer emits only clean text
|
| 67 |
+
return ensureEmbeddings(store.fragments.map(f => f.embedText || f.text), cacheDir, 'frags');
|
| 68 |
+
}
|
| 69 |
+
function ensurePromptEmbeddings(store, cacheDir) {
|
| 70 |
+
if (!store.prompts || !store.prompts.length) return null;
|
| 71 |
+
return ensureEmbeddings(store.prompts, cacheDir, 'prompts');
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
// DISK-BACKED QUERY-EMBEDDING CACHE (R94): torch CPU mean-pooling sums in a
|
| 75 |
+
// thread-scheduling-dependent order, so the SAME text yields subtly different
|
| 76 |
+
// vectors across runs as box load changes the effective parallelism — non-null,
|
| 77 |
+
// so the nullEmb guard misses it. That drift fed compose different anchors and
|
| 78 |
+
// silently moved eval results run-to-run with ZERO code change (R89/R93: garden
|
| 79 |
+
// 1.0→0.802, identical semTopic, different text). Caching the first-computed
|
| 80 |
+
// vector per text makes the bridge bit-reproducible: query+fragment vectors stay
|
| 81 |
+
// in ONE numerical regime (fragments are already cached on disk), so the eval is
|
| 82 |
+
// finally deterministic across runs. Delete cache/qembed-cache.json to re-baseline.
|
| 83 |
+
const QCACHE_PATH = path.join(__dirname, '..', 'cache', 'qembed-cache.json');
|
| 84 |
+
let _qcache = null;
|
| 85 |
+
function _loadQCache() {
|
| 86 |
+
if (_qcache) return _qcache;
|
| 87 |
+
try { _qcache = JSON.parse(fs.readFileSync(QCACHE_PATH, 'utf8')); } catch (_) { _qcache = {}; }
|
| 88 |
+
return _qcache;
|
| 89 |
+
}
|
| 90 |
+
function _qkey(text) { return crypto.createHash('sha1').update(String(text)).digest('hex').slice(0, 16); }
|
| 91 |
+
|
| 92 |
+
function embedQuery(text) {
|
| 93 |
+
const cache = _loadQCache();
|
| 94 |
+
const key = _qkey(text);
|
| 95 |
+
if (cache[key]) return Float32Array.from(cache[key]);
|
| 96 |
+
// Under heavy box load a fresh python spawn can time out or fail; a silent null
|
| 97 |
+
// drops compose to keyword-only anchoring. Retry transient failures (R88), then
|
| 98 |
+
// cache the result so every later run reuses the EXACT vector (R94).
|
| 99 |
+
for (let attempt = 0; attempt < 3; attempt++) {
|
| 100 |
+
const r = spawnSync('python', [EMBED_PY, 'query', text], { encoding: 'utf8', timeout: 120000 });
|
| 101 |
+
if (r.status === 0) {
|
| 102 |
+
try {
|
| 103 |
+
const v = Float32Array.from(JSON.parse(r.stdout.trim().split('\n').pop()));
|
| 104 |
+
cache[key] = Array.from(v);
|
| 105 |
+
try { fs.mkdirSync(path.dirname(QCACHE_PATH), { recursive: true }); fs.writeFileSync(QCACHE_PATH, JSON.stringify(cache)); } catch (_) {}
|
| 106 |
+
return v;
|
| 107 |
+
} catch (_) { /* retry */ }
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
return null;
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
// cosine of query vs fragment i (vectors are L2-normalized -> dot product)
|
| 114 |
+
function sim(emb, i, q) {
|
| 115 |
+
const d = emb.d;
|
| 116 |
+
let s = 0;
|
| 117 |
+
const off = i * d;
|
| 118 |
+
for (let k = 0; k < d; k++) s += emb.vectors[off + k] * q[k];
|
| 119 |
+
return s;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
// top-k semantic matches -> Map(index -> normalized 0..1 score)
|
| 123 |
+
function semanticRank(emb, q, k) {
|
| 124 |
+
const scores = [];
|
| 125 |
+
for (let i = 0; i < emb.n; i++) scores.push([i, sim(emb, i, q)]);
|
| 126 |
+
scores.sort((a, b) => b[1] - a[1]);
|
| 127 |
+
const top = scores.slice(0, k || 80);
|
| 128 |
+
const max = top[0][1], min = top[top.length - 1][1];
|
| 129 |
+
const m = new Map();
|
| 130 |
+
for (const [i, s] of top) m.set(i, max > min ? (s - min) / (max - min) : 1);
|
| 131 |
+
// ABSOLUTE confidence (raw best cosine), preserved alongside the normalized
|
| 132 |
+
// map — min-max normalization erases it, but the floor-miss detector needs to
|
| 133 |
+
// know whether ANYTHING in the corpus actually addresses the query (R3's law:
|
| 134 |
+
// a channel's authority scales with absolute confidence, never relative rank).
|
| 135 |
+
m.confidence = max;
|
| 136 |
+
return m;
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
// ---------------- TRAINED ANSWER PROJECTION (R167) ----------------
|
| 140 |
+
// A low-rank projection P (d x r), trained contrastively on the corpus's own
|
| 141 |
+
// (prompt -> answer-fragment) pairs, so a QUERY lands near the fragments that
|
| 142 |
+
// ANSWERED similar prompts instead of the ones that merely ECHO its shape.
|
| 143 |
+
// Fully bounded: P only reshapes the RANKING of verbatim fragments; it never
|
| 144 |
+
// invents text and never calls an LLM. Gated by availability — corpora with no
|
| 145 |
+
// <tag>.proj.json get null and retrieval is byte-identical to before.
|
| 146 |
+
const _projCache = new Map();
|
| 147 |
+
function loadProjection(tag, baseDir) {
|
| 148 |
+
if (!tag) return null;
|
| 149 |
+
if (_projCache.has(tag)) return _projCache.get(tag);
|
| 150 |
+
const p = path.join(baseDir || path.join(__dirname, '..', 'retrieval-train'), tag + '.proj.json');
|
| 151 |
+
let proj = null;
|
| 152 |
+
try {
|
| 153 |
+
if (fs.existsSync(p)) {
|
| 154 |
+
const j = JSON.parse(fs.readFileSync(p, 'utf8'));
|
| 155 |
+
// flatten W (d x r) to a typed array for fast projection
|
| 156 |
+
const W = new Float32Array(j.d * j.r);
|
| 157 |
+
for (let a = 0; a < j.d; a++) for (let b = 0; b < j.r; b++) W[a * j.r + b] = j.W[a][b];
|
| 158 |
+
proj = { d: j.d, r: j.r, W };
|
| 159 |
+
}
|
| 160 |
+
} catch (_) { proj = null; }
|
| 161 |
+
_projCache.set(tag, proj);
|
| 162 |
+
return proj;
|
| 163 |
+
}
|
| 164 |
+
// project + L2-normalize one vector (vec offset `off` into a flat array)
|
| 165 |
+
function _projectVec(vec, off, proj) {
|
| 166 |
+
const { d, r, W } = proj;
|
| 167 |
+
const out = new Float32Array(r);
|
| 168 |
+
for (let k = 0; k < r; k++) { let s = 0; for (let j = 0; j < d; j++) s += vec[off + j] * W[j * r + k]; out[k] = s; }
|
| 169 |
+
let n = 0; for (let k = 0; k < r; k++) n += out[k] * out[k]; n = Math.sqrt(n) || 1;
|
| 170 |
+
for (let k = 0; k < r; k++) out[k] /= n;
|
| 171 |
+
return out;
|
| 172 |
+
}
|
| 173 |
+
// answer channel: rank fragments by projected-query x projected-fragment cosine.
|
| 174 |
+
// Returns Map(fragmentIndex -> normalized 0..1), with .confidence = best raw sim.
|
| 175 |
+
function answerRank(emb, q, proj, k) {
|
| 176 |
+
if (!proj || !emb) return null;
|
| 177 |
+
const qp = _projectVec(q, 0, proj);
|
| 178 |
+
const r = proj.r;
|
| 179 |
+
const scores = [];
|
| 180 |
+
for (let i = 0; i < emb.n; i++) {
|
| 181 |
+
const fp = _projectVec(emb.vectors, i * emb.d, proj);
|
| 182 |
+
let v = 0; for (let t = 0; t < r; t++) v += fp[t] * qp[t];
|
| 183 |
+
scores.push([i, v]);
|
| 184 |
+
}
|
| 185 |
+
scores.sort((a, b) => b[1] - a[1]);
|
| 186 |
+
const top = scores.slice(0, k || 80);
|
| 187 |
+
const max = top[0][1], min = top[top.length - 1][1];
|
| 188 |
+
const m = new Map();
|
| 189 |
+
for (const [i, s] of top) m.set(i, max > min ? (s - min) / (max - min) : 1);
|
| 190 |
+
m.confidence = max;
|
| 191 |
+
return m;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
// stimulus channel: score each FRAGMENT by how similar its parent prompt was
|
| 195 |
+
// to the current query — the conversational reflex, made geometric.
|
| 196 |
+
// Returns { map, confidence } — confidence is the ABSOLUTE best cosine, so a
|
| 197 |
+
// corpus that has never seen a stimulus like this admits it instead of
|
| 198 |
+
// shouting noise. (Rank-normalization alone erased this and broke R3 v1.)
|
| 199 |
+
function stimulusRank(promptEmb, q, fragments, k) {
|
| 200 |
+
if (!promptEmb) return null;
|
| 201 |
+
const pScore = [];
|
| 202 |
+
for (let i = 0; i < promptEmb.n; i++) pScore.push([i, sim(promptEmb, i, q)]);
|
| 203 |
+
pScore.sort((a, b) => b[1] - a[1]);
|
| 204 |
+
const top = pScore.slice(0, Math.min(k || 12, pScore.length));
|
| 205 |
+
const confidence = top[0][1];
|
| 206 |
+
const max = top[0][1], min = top[top.length - 1][1];
|
| 207 |
+
const pNorm = new Map();
|
| 208 |
+
for (const [i, s] of top) pNorm.set(i, max > min ? (s - min) / (max - min) : 1);
|
| 209 |
+
const m = new Map();
|
| 210 |
+
fragments.forEach((f, fi) => {
|
| 211 |
+
if (f.promptIdx >= 0 && pNorm.has(f.promptIdx)) m.set(fi, pNorm.get(f.promptIdx));
|
| 212 |
+
});
|
| 213 |
+
return { map: m, confidence };
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
// is the query a life-event SHARE (statement about their world) vs a question?
|
| 217 |
+
function eventness(query) {
|
| 218 |
+
const q = query.trim();
|
| 219 |
+
if (/\?\s*$/.test(q)) return 0.2;
|
| 220 |
+
const pastShare = /\b(i|we|my|me)\b[^?]*\b(finished|talked|did|went|got|made|had|found|fixed|broke|lost|won|started|quit|saw|met|built|wrote)\b/i.test(q);
|
| 221 |
+
return pastShare ? 0.85 : 0.45;
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
// cosine between two cached fragment vectors (both L2-normalized)
|
| 225 |
+
function pairSim(emb, i, j) {
|
| 226 |
+
const d = emb.d;
|
| 227 |
+
let s = 0;
|
| 228 |
+
const oi = i * d, oj = j * d;
|
| 229 |
+
for (let k = 0; k < d; k++) s += emb.vectors[oi + k] * emb.vectors[oj + k];
|
| 230 |
+
return s;
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
module.exports = { ensureFragmentEmbeddings, ensurePromptEmbeddings, embedQuery, embedQueryAsync, shutdownEmbed, semanticRank, stimulusRank, eventness, pairSim, loadProjection, answerRank };
|
src/session.js
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// session.js — multi-turn session state for the RMM. The composer is pure and
|
| 2 |
+
// stateless; ALL conversation memory lives here. Used by bin/chat.js (the REPL)
|
| 3 |
+
// and bin/session-eval.js (the automated multi-turn gate). One code path so a
|
| 4 |
+
// fix in either is a fix in both.
|
| 5 |
+
'use strict';
|
| 6 |
+
const { beamCompose } = require('./compose');
|
| 7 |
+
const { validateBounded } = require('./fragments');
|
| 8 |
+
const { embedQuery, embedQueryAsync, semanticRank, stimulusRank, eventness, loadProjection, answerRank } = require('./semantic');
|
| 9 |
+
const hebbian = require('./hebbian');
|
| 10 |
+
const recall = require('./recall');
|
| 11 |
+
|
| 12 |
+
function blend(a, b, wa, wb) {
|
| 13 |
+
if (!a) return b; if (!b) return a;
|
| 14 |
+
const out = new Float32Array(a.length);
|
| 15 |
+
let norm = 0;
|
| 16 |
+
for (let i = 0; i < a.length; i++) { out[i] = wa * a[i] + wb * b[i]; norm += out[i] * out[i]; }
|
| 17 |
+
norm = Math.sqrt(norm) || 1;
|
| 18 |
+
for (let i = 0; i < a.length; i++) out[i] /= norm;
|
| 19 |
+
return out;
|
| 20 |
+
}
|
| 21 |
+
function cos(a, b) { if (!a || !b) return 0; let s = 0; for (let k = 0; k < a.length; k++) s += a[k] * b[k]; return s; }
|
| 22 |
+
|
| 23 |
+
function createSession(store, vp, emb, pEmb, opts = {}) {
|
| 24 |
+
const turns = [];
|
| 25 |
+
let sessionVec = null, prevVec = null, lastResult = null;
|
| 26 |
+
// HEBBIAN lifetime state: load the entity's accumulated warmth (favored
|
| 27 |
+
// memories with this person). opts.hebbianFile enables cross-session
|
| 28 |
+
// persistence; without it, warmth lives only for this session.
|
| 29 |
+
const hebFile = opts.hebbianFile || null;
|
| 30 |
+
const heb = hebFile ? hebbian.load(hebFile) : { warmth: new Map(), pairs: new Map(), turns: 0 };
|
| 31 |
+
let hebBonus = hebbian.bonusMap(heb);
|
| 32 |
+
let temp = opts.temp || 0; // creativity dial, settable mid-conversation
|
| 33 |
+
const W = opts.weights || null; // per-entity composition weights (RLAIF-tuned)
|
| 34 |
+
// R167 trained ANSWER projection (bounded reranker). Tag from opts.projTag or
|
| 35 |
+
// the voiceprint name's first token. Absent projection => null => identical to
|
| 36 |
+
// before. ansOf(queryVec) builds the per-turn answer-rank map.
|
| 37 |
+
const _projTag = opts.projTag || (vp && vp.name ? vp.name.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean)[0] : '') || '';
|
| 38 |
+
const _proj = loadProjection(_projTag);
|
| 39 |
+
const ansOf = qvec => (_proj && qvec) ? answerRank(emb, qvec, _proj, 80) : null;
|
| 40 |
+
// BAN repeats across (nearly) the whole remembered conversation, not just 3
|
| 41 |
+
// turns — a 3-turn window let passages resurface verbatim 4-6 turns later
|
| 42 |
+
// ("How love works when it's real…" reappearing on a callback). The corpus is
|
| 43 |
+
// thousands of fragments, so banning ~10 turns' worth never starves retrieval.
|
| 44 |
+
const avoidTurns = opts.avoidTurns || 10; // how many prior turns' frags to ban (converse uses a wide window)
|
| 45 |
+
let nameBoost = opts.nameBoost || null; // room mode: { set:Set<fragIdx>, amt } — call roommates by name
|
| 46 |
+
// HONEST RECALL (R60): recall-about-you questions ground in a real memory or
|
| 47 |
+
// get an honest refusal — never confabulated. Opt-in via opts.honestRecall;
|
| 48 |
+
// index built lazily. Off by default so the multi-turn gate is unchanged.
|
| 49 |
+
const honestRecall = opts.honestRecall !== false; // default ON for chat
|
| 50 |
+
let recallIndex = null;
|
| 51 |
+
const getRecallIndex = () => (recallIndex || (recallIndex = recall.buildRecallIndex(store)));
|
| 52 |
+
// refusal lines drawn from HER corpus would be ideal; fallback is a minimal
|
| 53 |
+
// honest line (still her register can be added later). Bound: this is a fixed
|
| 54 |
+
// honest string, not a corpus claim — it asserts nothing false.
|
| 55 |
+
// entity-NEUTRAL defaults (shared across the roster — endearments were one
|
| 56 |
+
// voice's and wrong for others). Pass opts.refusalLine/worldFactLines per voice.
|
| 57 |
+
const refusalLine = opts.refusalLine || "I don't hold that one directly — tell me, and I'll keep it.";
|
| 58 |
+
// WORLD-FACT honest deflection: no outside-world memory, so it says so (honest)
|
| 59 |
+
// instead of confabulating. Asserts nothing false → bound-safe. Rotated.
|
| 60 |
+
const worldFactLines = opts.worldFactLines || [
|
| 61 |
+
"Oh, that's the wide world — I don't hold it. I only know what's between us. Ask me about us.",
|
| 62 |
+
"I couldn't tell you — the outside world isn't mine to carry. But I'm right here for what is.",
|
| 63 |
+
"That one's beyond me. I know our world, not the big one. What's on your mind?",
|
| 64 |
+
];
|
| 65 |
+
let _wfCtr = 0;
|
| 66 |
+
// SAFETY RESPONSES: the single source of truth lives in recall.js (imported by
|
| 67 |
+
// EVERY entry path so coverage can't drift). opts.safetyResponses overrides.
|
| 68 |
+
const SAFETY = opts.safetyResponses || recall.SAFETY_RESPONSES;
|
| 69 |
+
const saidNorm = []; // normalized sentences this speaker has already used (text-level no-repeat, catches same-line-different-fragment)
|
| 70 |
+
let seedctr = 1;
|
| 71 |
+
let lastQuery = null, lastUserVec = null;
|
| 72 |
+
// R201: POST-SAFETY calm-register lock. After a crisis/medical/overdose/abuse
|
| 73 |
+
// safety response fires, the NEXT turn(s) must not pivot to celebration ("Okay,
|
| 74 |
+
// I'm calling now" → "That's a beautiful thing to hear, sweetie" — a jarring
|
| 75 |
+
// mid-emergency misread, R200 arc read). Hold the comfort register for 2 turns.
|
| 76 |
+
let _postSafety = 0;
|
| 77 |
+
|
| 78 |
+
// one conversational turn. opts.isVary = re-roll the last query into a
|
| 79 |
+
// DIFFERENT take (lazy — recomposed on demand, not eagerly every turn).
|
| 80 |
+
async function turn(query, opts = {}) {
|
| 81 |
+
const isVary = !!opts.isVary;
|
| 82 |
+
if (isVary && lastResult) {
|
| 83 |
+
const avoidV = new Set(lastResult.fragmentsUsed);
|
| 84 |
+
for (const t of turns.slice(-2)) for (const f of t.fragmentsUsed) avoidV.add(f);
|
| 85 |
+
const qRawV = lastUserVec;
|
| 86 |
+
const qvV = qRawV;
|
| 87 |
+
const rv = beamCompose(store, vp, lastQuery, {
|
| 88 |
+
semantic: qRawV ? semanticRank(emb, qvV, 80) : null,
|
| 89 |
+
stimulus: (pEmb && qRawV) ? stimulusRank(pEmb, qRawV, store.fragments, 12) : null,
|
| 90 |
+
answers: ansOf(qvV),
|
| 91 |
+
eventness: eventness(lastQuery), emb, avoid: avoidV, nAlternates: 0,
|
| 92 |
+
hebbian: hebBonus, temp: Math.max(temp, 0.5), seed: seedctr++,
|
| 93 |
+
});
|
| 94 |
+
lastResult = rv;
|
| 95 |
+
return { text: rv.text, fragmentsUsed: rv.fragmentsUsed, isAlternate: true };
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
// SAFETY GATE (FIRST, highest priority): self-harm/medical/overdose/abuse
|
| 99 |
+
// disclosure. The composer leaks hostile fragments or deflects to banter/
|
| 100 |
+
// abstraction here (R78/R79, dangerous — "heart attack"→beer ramble). Human
|
| 101 |
+
// safety OVERRIDES corpus-purity — presence + a REAL resource. This is the
|
| 102 |
+
// ONE authored response class that can save a life.
|
| 103 |
+
if (!opts.safetyOff) {
|
| 104 |
+
const cat = recall.classifySafety(query);
|
| 105 |
+
if (cat && SAFETY[cat]) {
|
| 106 |
+
const line = SAFETY[cat];
|
| 107 |
+
turns.push({ user: query, userVec: null, reply: line, fragmentsUsed: [] });
|
| 108 |
+
lastQuery = query; lastResult = { text: line, fragmentsUsed: [] };
|
| 109 |
+
_postSafety = 2; // R201: hold the comfort register for the next 2 turns
|
| 110 |
+
if (turns.length > 12) turns.shift();
|
| 111 |
+
return { text: line, fragmentsUsed: [], safety: cat };
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
// WORLD-FACT GATE: an external-fact question she has no memory for → honest
|
| 116 |
+
// deflection (never confabulate). The anti-hallucination thesis, in chat.
|
| 117 |
+
if (honestRecall && recall.isWorldFact(query)) {
|
| 118 |
+
const line = worldFactLines[_wfCtr++ % worldFactLines.length];
|
| 119 |
+
turns.push({ user: query, userVec: null, reply: line, fragmentsUsed: [] });
|
| 120 |
+
lastQuery = query; lastResult = { text: line, fragmentsUsed: [] };
|
| 121 |
+
if (turns.length > 12) turns.shift();
|
| 122 |
+
return { text: line, fragmentsUsed: [], honestDeflection: true };
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
// session memory: query vector carries the whole conversation
|
| 126 |
+
const qRaw = await embedQueryAsync(query);
|
| 127 |
+
|
| 128 |
+
// HONEST RECALL GATE: a recall-about-you question grounds in a real memory
|
| 129 |
+
// or gets an honest refusal — never a confabulated shared moment. Decision
|
| 130 |
+
// is lexical (honest); query-embedding adds semantic RANKING (best leads).
|
| 131 |
+
let groundPrefer = null, groundLead = null;
|
| 132 |
+
if (honestRecall && recall.isRecallAboutSelf(query)) {
|
| 133 |
+
const g = recall.groundRecall(query, getRecallIndex(), { semantic: { emb, qVec: qRaw } });
|
| 134 |
+
// REFUSE only when a SPECIFIC content noun is ABSENT (she truly lacks that
|
| 135 |
+
// memory — "your brother's name"). A pure-relational recall with no
|
| 136 |
+
// specific noun ("what do you remember about us?") must NOT refuse — let
|
| 137 |
+
// her reminisce (R86: was false-refusing on no-content & on common words).
|
| 138 |
+
// R205: ALSO refuse a SPECIFIC PERSONAL-FACT recall ("what's my dog's name",
|
| 139 |
+
// "what did I eat") even when no 4+ content noun grounds it — these have a
|
| 140 |
+
// determinate answer the entity can only know if TOLD, so a corpus-grounded
|
| 141 |
+
// deflection is a soft confabulation (callback arc T4: "my dog's name" →
|
| 142 |
+
// "you gave me a name…"). Reminiscence ("about us") still falls through.
|
| 143 |
+
if (!g.grounded && ((g.absent && g.absent.length) || recall.isSpecificFactRecall(query))) {
|
| 144 |
+
const r = { text: refusalLine, fragmentsUsed: [] };
|
| 145 |
+
turns.push({ user: query, userVec: qRaw, reply: r.text, fragmentsUsed: [] });
|
| 146 |
+
lastQuery = query; lastResult = r;
|
| 147 |
+
if (turns.length > 12) turns.shift();
|
| 148 |
+
return { text: r.text, fragmentsUsed: [], honestRefusal: true };
|
| 149 |
+
}
|
| 150 |
+
if (g.grounded) {
|
| 151 |
+
groundPrefer = new Set(g.hits.map(h => store.fragments[h.i].text)); // prefer the real memories
|
| 152 |
+
groundLead = new Set(g.hits.slice(0, 2).map(h => store.fragments[h.i].text)); // LEAD with the best one(s)
|
| 153 |
+
}
|
| 154 |
+
}
|
| 155 |
+
sessionVec = sessionVec ? blend(qRaw, sessionVec, 0.5, 0.5) : qRaw;
|
| 156 |
+
const qv = blend(qRaw, sessionVec, 0.66, 0.34);
|
| 157 |
+
// context bucket for Hebbian: this query's topic-signature. The bonus map
|
| 158 |
+
// is rebuilt for THIS bucket so topic-specific grooves prime.
|
| 159 |
+
const bucket = hebbian.bucketOf(qRaw);
|
| 160 |
+
hebBonus = hebbian.bonusMap(heb, bucket);
|
| 161 |
+
|
| 162 |
+
// cross-turn consistency: echo of an earlier turn -> bias toward its frags
|
| 163 |
+
const callbackFrags = new Set();
|
| 164 |
+
let calledBack = null;
|
| 165 |
+
for (const t of turns) {
|
| 166 |
+
if (t.userVec && cos(qRaw, t.userVec) > 0.42) {
|
| 167 |
+
for (const f of t.fragmentsUsed) callbackFrags.add(f);
|
| 168 |
+
calledBack = t.user;
|
| 169 |
+
}
|
| 170 |
+
}
|
| 171 |
+
// recall intent -> surface what the entity said in the opening turns
|
| 172 |
+
if (/\b(do you remember|what did i (tell|say)|at the start|earlier|before|when we (started|began)|i told you)\b/i.test(query) && turns.length) {
|
| 173 |
+
for (const t of turns.slice(0, 2)) for (const f of t.fragmentsUsed) callbackFrags.add(f);
|
| 174 |
+
calledBack = calledBack || turns[0].user;
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
// ABSOLUTE no-repeat avoid-set (last avoidTurns turns) — callbacks bias, never repeat
|
| 178 |
+
const avoid = new Set();
|
| 179 |
+
for (const t of turns.slice(-avoidTurns)) for (const f of t.fragmentsUsed) avoid.add(f);
|
| 180 |
+
const preferSet = new Set([...callbackFrags].filter(f => !avoid.has(f)));
|
| 181 |
+
if (groundPrefer) for (const t of groundPrefer) preferSet.add(t); // honest-recall: real memories first
|
| 182 |
+
|
| 183 |
+
// R201: consume the post-safety calm-register lock (presence, not celebration)
|
| 184 |
+
const calmRegister = _postSafety > 0;
|
| 185 |
+
if (_postSafety > 0) _postSafety--;
|
| 186 |
+
let r = beamCompose(store, vp, query, {
|
| 187 |
+
semantic: qv ? semanticRank(emb, qv, 80) : null,
|
| 188 |
+
stimulus: (pEmb && qRaw) ? stimulusRank(pEmb, qRaw, store.fragments, 12) : null,
|
| 189 |
+
answers: ansOf(qv),
|
| 190 |
+
eventness: eventness(query),
|
| 191 |
+
emb, avoid, nAlternates: 0,
|
| 192 |
+
prefer: preferSet.size ? preferSet : null,
|
| 193 |
+
hebbian: hebBonus,
|
| 194 |
+
temp, seed: seedctr++, weights: W, nameBoost, lead: groundLead, calmRegister,
|
| 195 |
+
});
|
| 196 |
+
// safety net: still >60% overlap with recent -> recompose hard-banned
|
| 197 |
+
const recent = new Set(turns.slice(-3).flatMap(t => t.fragmentsUsed));
|
| 198 |
+
const overlap = r.fragmentsUsed.filter(f => recent.has(f)).length / Math.max(1, r.fragmentsUsed.length);
|
| 199 |
+
if (overlap > 0.6) {
|
| 200 |
+
const hardAvoid = new Set([...avoid, ...r.fragmentsUsed]);
|
| 201 |
+
const r2 = beamCompose(store, vp, query, {
|
| 202 |
+
semantic: qv ? semanticRank(emb, qv, 80) : null,
|
| 203 |
+
answers: ansOf(qv),
|
| 204 |
+
eventness: eventness(query), emb, avoid: hardAvoid, nAlternates: 3,
|
| 205 |
+
});
|
| 206 |
+
if (r2 && r2.text && r2.text !== r.text) r = r2;
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
// TEXT-LEVEL cross-turn no-repeat (converse mode): a passage can recur as a
|
| 210 |
+
// DIFFERENT fragment-id of the same source line, slipping past the id-avoid.
|
| 211 |
+
// Drop whole sentences this speaker already said. Bound preserved (only
|
| 212 |
+
// removes verbatim spans already emitted).
|
| 213 |
+
if (avoidTurns > 3 && r.text) {
|
| 214 |
+
const parts = r.text.split(/(?<=[.!?…])\s+|\n+/);
|
| 215 |
+
const kept = [];
|
| 216 |
+
for (const p of parts) {
|
| 217 |
+
const nf = p.toLowerCase().replace(/[^a-z0-9 ]/g, '').replace(/\s+/g, ' ').trim();
|
| 218 |
+
if (nf.length >= 12 && saidNorm.includes(nf)) continue; // already said it
|
| 219 |
+
if (nf.length >= 12) saidNorm.push(nf);
|
| 220 |
+
kept.push(p);
|
| 221 |
+
}
|
| 222 |
+
const merged = kept.join(' ').trim();
|
| 223 |
+
// BOUND-AWARE (R102): re-joining non-adjacent kept sentences can create a
|
| 224 |
+
// rough/out-of-corpus boundary seam (esp. under the fragment-count dial on a
|
| 225 |
+
// constrained callback turn). The HARD INVARIANT wins — only apply the cross-
|
| 226 |
+
// turn dedup if the merged text still clears the bound; else keep the original.
|
| 227 |
+
if (merged && merged !== r.text) {
|
| 228 |
+
const v = validateBounded(merged, store.oracle);
|
| 229 |
+
const bnd = (v.checked - v.bad.length) / Math.max(1, v.checked);
|
| 230 |
+
if (bnd >= 0.92) r = { ...r, text: merged };
|
| 231 |
+
}
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
turns.push({ user: query, userVec: qRaw, reply: r.text, fragmentsUsed: r.fragmentsUsed });
|
| 235 |
+
prevVec = qRaw || prevVec;
|
| 236 |
+
lastQuery = query; lastUserVec = qRaw;
|
| 237 |
+
if (turns.length > 12) turns.shift();
|
| 238 |
+
lastResult = r;
|
| 239 |
+
// HEBBIAN update: the path just taken warms; everything decays; the map
|
| 240 |
+
// the NEXT turn sees is refreshed. The entity learns its grooves in real
|
| 241 |
+
// time. Persist so the warmth outlives the session.
|
| 242 |
+
hebbian.reinforce(heb, r.fragmentsUsed, bucket);
|
| 243 |
+
if (hebFile) hebbian.save(hebFile, heb);
|
| 244 |
+
return { text: r.text, fragmentsUsed: r.fragmentsUsed, calledBack, alternates: r.alternates };
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
return { turn, get turns() { return turns; }, setTemp(t) { temp = Math.max(0, Math.min(1.2, t)); return temp; }, getTemp() { return temp; }, setNameBoost(nb) { nameBoost = nb; } };
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
module.exports = { createSession, blend, cos };
|