Skip to content

SatisfactionCapture: the FAILURES and Upgrades legs are English-phrase-only, so on a non-Latin-script install both corpora stay empty and look healthy #2165

Open

Description

@IvanLeontev-stack

Version

LifeOS 7.40.4 / SatisfactionCapture 1.4.3 — also reproduced on a clean checkout of main at 5e2f2e8c.

What is broken

SatisfactionCapture has two deterministic legs that feed the self-healing corpora: detectExplicitCorrection() → captureFailure() → FAILURES, and detectStandingDirective() → addUpgrade() → the Upgrades store. Both match English literals only — a list of English phrases, plus \b-anchored ASCII alternations. On an install whose principal writes in a non-Latin script, both functions return null for every prompt of every session: no complaint is ever captured, no standing directive ever reaches the Upgrades store, and nothing anywhere records that the detectors were mute. The two corpora simply stay empty, which is also what a flawless month looks like, so the failure is invisible from the dashboard, from the corpora, and from the hook's own output.

Where (file:line)

hooks/SatisfactionCapture.hook.ts:219 (CORRECTION_PHRASES, English strings), :235 (WORK_REFERENCE, \b-anchored ASCII), :267 (DIRECTIVE_PATTERNS, English phrases plus \b(always|never) (do|use|…)\b). Writers: :478 (standing directive → addUpgrade) and :497 (correction → captureFailure).

Repro on a clean tree

# fresh checkout, nothing patched
gh api repos/danielmiessler/LifeOS/tarball/5e2f2e8c > main.tar.gz
mkdir clean && tar xzf main.tar.gz -C clean --strip-components=1

cat > r1.ts <<'TS'
const ROOT = process.env.LIFEOS_ROOT!;
const { detectExplicitCorrection, detectStandingDirective } =
  await import(`${ROOT}/hooks/SatisfactionCapture.hook.ts`);
const cases = [
  ["correction EN", detectExplicitCorrection, "no, that's wrong, you didn't do what I asked"],
  ["correction RU", detectExplicitCorrection, "нет, это неправильно, ты сделал не то, о чём я просил"],
  ["directive  EN", detectStandingDirective, "from now on always show me the verification"],
  ["directive  RU", detectStandingDirective, "с этого момента всегда показывай мне проверку"],
] as const;
for (const [label, fn, text] of cases) console.log(label + ":", fn(text) !== null);
TS

LIFEOS_ROOT="$PWD/clean/LifeOS/install" bun r1.ts

Each Russian sentence is a literal translation of the English one above it.

Negative control

On unpatched main@5e2f2e8c the probe is red: the two English sentences are detected, the two translations of those same sentences are not.

correction EN: true
correction RU: false
directive  EN: true
directive  RU: false

Stated without reference to any fix: a principal who says "from now on always show me the verification" gets one row in the Upgrades store; a principal who says the same sentence in Russian gets zero, every time, for the life of the install.

Suggested fix

Not an i18n configuration layer — #1706 was declined for exactly that and the reasoning holds. Two smaller, independent things:

  1. Make the patterns safe to extend. The \b anchors here carry the same latent trap matchSkills had before AlgorithmNudge skill routing is silently mute: index staleness ignores content, and matchSkills assumes English morphology #1787 and AlgorithmNudge had before AlgorithmNudge trigger vocabulary is English-only — non-English prompts never fire the always-on prompt-side rows #1865: \b is ASCII-only in JS, so the first non-Latin term anyone adds to these lists silently never matches. The same Unicode-property boundary construction applied here costs nothing in English behaviour, and turns "extend the list" into a change that actually works.
  2. Make a mute detector observable. The hook writes nothing when both legs return null, so nothing distinguishes "no corrections this session" from "the detector cannot fire on this principal's prompts". One counter, wherever it fits.

Vocabulary can then arrive one language at a time from whoever needs it, the way the German depth list did in #1865. Untested as a patch — I have not written it.

Related

#1865, #1787, #1703, #1706.


  • I searched open and closed issues for this defect.
  • The repro runs against a clean tree of the version above, not against a modified install.
  • The pasted output carries no personal data — no names, no home paths, no tokens, no own content. Both Russian strings are invented test sentences.

Activity

  1. jacobo-ortiz commented on Sep 24, 2026

    @jacobo-ortiz

    Adding one data point from a Latin-script install (the principal writes in Spanish), filed by mistake as #2236 before I saw this issue:

    • The praise fast-path has the same English-only shape: POSITIVE_PRAISE_WORDS / POSITIVE_PHRASES. In a sandbox (LIFEOS_DIR on a temp dir), "perfect" writes a rating and "perfecto", "excelente", "muy bien" write nothing. On that install ratings.jsonl got no new line for two months, and nothing flagged it.
    • In a Latin script the correction leg half-works: the ^no[,.] opener catches "No, ...", but the most frequent real corrections that month ("estás mezclando cosas", "no es lo que te pedí") were missed.
    • Checking candidate phrases against the principal's own recent prompts before adding them paid off: "no funciona" looks like a correction but usually reports a third party's broken thing.

    So whatever vocabulary mechanism lands (#2230) should cover the praise lists too, not just the correction and directive lists.

  2. gjaworski86 commented on Sep 25, 2026

    @gjaworski86

    Confirming from a Polish install, with numbers from about 2600 of the principal's own prompts (May to September).

    • The English correction leg fired twice in four months, and both hits were false. In Polish, "No," at the start of a message means "well, okay", so ^no[,.] turned an approval into a FAILURES capture. The other hit was an English phrase inside a pasted, quoted review.
    • Matching only the principal's own words matters as much as the language. Before matching we strip fenced and inline code, text in quotation marks, quoted lines and <pasted_content> blocks. We also cap the prompt at 800 chars: over machine prompts that paste whole transcripts, the Polish phrases would otherwise fire 207 times in 1945.
    • SessionHarvester has the same problem, only worse. Its CORRECTION_PATTERNS (/wait,?\s+/, /actually,?\s+/, ...) run on every user entry in the transcript. That includes text the harness injects: skill bodies, task notifications, compaction summaries and memory-curator prompts. All 309 "corrections" it harvested here were that, and none came from the principal. MemoryRetriever then fed them back into context on every turn.

    Related, #1888: the imperative list from #1889 catches no Polish answers. All 9 rows in our ratings ledger were answers to numbered points ("1 - tak, zrób to"). What worked better: if the previous response offered point N, a leading N answers it, and a tail with its own item numbers or a question mark is an answer too.

    We did this with one shared detector module (corrections + praise) used by SatisfactionCapture, PromptProcessing and SessionHarvester. It is close to the #2230 idea, but inline rather than per-locale files.

  3. danielmiessler commented on Oct 3, 2026

    @danielmiessler
    Owner

    Thanks @IvanLeontev-stack, and @jacobo-ortiz and @gjaworski86 for the Spanish and Polish numbers. Confirmed: the correction, praise and directive vocabularies now live in one shared classifier (LIFEOS/ERRATA/Signals.ts), but they are still English-only, so a non-English install captures little or nothing. Per-language vocabulary is a design change, so this stays open and tracked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions