fix(memory): word-boundary + compound-aware failure-signal detection in episode classifier - #4080
Merged
rysweet merged 1 commit intoJul 16, 2026
Conversation
…in episode classifier The episode-ingestion classifier (issue #2327) detected failure/error episodes with a naive substring scan over {error, failed, failure, panic, exception}. Substring matching mis-fired on look-alikes — "exceptional" (⊃ "exception"), "hispanic" (⊃ "panic"), "terror"/"mirror" (⊃ "error") — storing those benign or POSITIVE episodes at full importance (0.9) as ActionFailures. That phantom- failure noise then flowed into distillation, degrading fact-yield quality and reasoner reliability (the standing cognition-improvement goal's axes). Replace the substring scan with word-boundary matching in two complementary passes, both anchored at word boundaries so coincidental substrings never fire: 1. lowercase stem + inflection — the {error, fail, failure, panic, panick, exception} stems plus benign inflectional suffixes ("", s, es, d, ed, ing), so genuine inflections (errors, failed, failing, panicked, panicking, exceptions, failures) still fire while derivational look-alikes (exceptional) do not; and 2. case-sensitive compound PascalCase type names (ParseError, IoError, NullPointerException) — idiomatic error/exception type identifiers whose stem sits at the END of a delimiter-less compound the prefix rule cannot see. The capitalised segment distinguishes a real type name from the lowercase look-alike "terror". Net effect: keeps every genuine failure word the substring scan caught (plus compound type names) while dropping the false positives that polluted episodic memory and distillation. - classifier.rs: FAILURE_STEMS / INFLECTIONAL_SUFFIXES / COMPOUND_FAILURE_SUFFIXES, word_is_failure, word_is_compound_failure_typename, rewritten has_failure_signal (now takes original-case content); Rule 1 and sanitize_transcript pass through. - classifier_tests.rs: false-positive kills, inflection preservation, compound type-name coverage, and a case-sensitivity guard for the compound pass. - docs: episode-ingestion-classifier.md + episode-ingestion-policy.md Rule 1. - tests/qa-scenarios/classifier-failure-signal-fidelity.yaml: outside-in gate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
📊 Coverage Summary
Coverage data from CI run. Test files matching |
rysweet
deleted the
engineer/continuously-research-and-improve-your-own-cogn-70ab8541-1784091717-49fc35
branch
July 16, 2026 18:11
rysweet
added a commit
that referenced
this pull request
Jul 16, 2026
…#4080) The daemon derived the overseer meta-thread's `last_success` from `!panicked && report.errors == 0`, so a single transient per-intervention `act()` failure — documented as "isolated, never fatal" — pinned the meta-thread in "erroring", and a later healthy tick never cleared it. Add an additive `cycle_failed: bool` to `OverseerTickReport` that is set ONLY when the tick's OODA cycle itself fails (`run_cycle()` returns Err or the tick panics). Health is now derived from `!panicked && !cycle_failed`, so isolated act() errors are still counted in `errors` for feed visibility but no longer drive health. Because health is recomputed fresh each tick, the first clean tick flips the row from "erroring" back to "ok". - additive, `#[serde(default)]` — legacy feed records default to false, no SCHEMA_VERSION bump - structured tracing + daemon log now emit `cycle_failed` - regression tests cover erroring→ok recovery, run_cycle Err, panic, and legacy deserialization Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
rysweet
added a commit
that referenced
this pull request
Jul 16, 2026
…#4080) (#4214) The daemon derived the overseer meta-thread's `last_success` from `!panicked && report.errors == 0`, so a single transient per-intervention `act()` failure — documented as "isolated, never fatal" — pinned the meta-thread in "erroring", and a later healthy tick never cleared it. Add an additive `cycle_failed: bool` to `OverseerTickReport` that is set ONLY when the tick's OODA cycle itself fails (`run_cycle()` returns Err or the tick panics). Health is now derived from `!panicked && !cycle_failed`, so isolated act() errors are still counted in `errors` for feed visibility but no longer drive health. Because health is recomputed fresh each tick, the first clean tick flips the row from "erroring" back to "ok". - additive, `#[serde(default)]` — legacy feed records default to false, no SCHEMA_VERSION bump - structured tracing + daemon log now emit `cycle_failed` - regression tests cover erroring→ok recovery, run_cycle Err, panic, and legacy deserialization Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
rysweet
added a commit
that referenced
this pull request
Jul 17, 2026
…ures (#893) #4080 stopped an isolated per-intervention act() error from pinning the `overseer` meta-thread in "erroring", but a genuine `run_cycle()` failure whose cause is transient and external (an upstream 5xx, a timeout, a connection reset, a rate-limit) still latched "erroring" until a later clean tick — degrading the outer OODA stewardship loop even though nothing is actually wrong with Simard. This adds a bounded self-healing rung, purely additive to the post-#4080 baseline: - `wiring::is_transient` — a pure, fail-closed classifier over `OverseerError`. Only a `Capability` error whose detail names a known-retryable upstream fault is transient; every unknown detail and every non-`Capability` variant is fatal (SR-1). - `OverseerTickReport::transient_cycle_failure` — additive `#[serde(default)]` flag set only when a `cycle_failed` tick's error was transient (never on a panic). Legacy feed records deserialize to the conservative `false`; SCHEMA_VERSION is unchanged. - `OverseerThreadStatus::overseer_meta` — extended to consume the report plus a running consecutive-transient count and a configured ceiling: a transient failure within the ceiling maps to a self-clearing "backoff" (one cadence, no erroring counter); beyond the ceiling it escalates to "erroring" so a hard-down dependency cannot hide behind an infinite backoff (SR-2); fatal and panicked failures still map straight to "erroring". - `config::overseer_transient_backoff_ceiling{,_from}` — fail-safe env-injectable ceiling resolver (`SIMARD_OVERSEER_TRANSIENT_BACKOFF_CEILING`, default 3, always >= 1). - daemon loop owns the consecutive-transient counter across ticks (reset on a completed tick, incremented on a transient failure, unchanged on a fatal one). Docs (self-healing reference, activity-feed reference, index) move from "design — not yet implemented" to shipped. Structured tracing + the tick log carry the new `transient` signal; no println!/print!. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This was referenced Jul 17, 2026
rysweet
added a commit
that referenced
this pull request
Jul 17, 2026
…lity / fact-yield) (#4245) The episode-ingestion classifier (`memory_consolidation::classifier::classify`) decides, at every `store_episode` intake site, whether an event is operational noise (DROP), low-value bookkeeping (DOWN-SCOPE), or a meaningful episodic worth full-importance storage (STORE). Distillation later mines the STORED, high-importance episodes into semantic facts, so a misclassification here propagates straight into recall top-k. Rule 3's durable-completion signal scored a single token with a raw-substring `content_lc.contains("merged")`. That fired inside the git-status vocabulary `unmerged` / `submerged` — which name the OPPOSITE of a completed merge (an outstanding, un-completed one) — promoting benign/negative bookkeeping to a durable `ActionCompleted` episode at 0.7 importance. Distillation would then mine that phantom completion into a phantom "action completed" fact, polluting recall top-k and dragging recall precision down. The `merged` signal now matches at WORD BOUNDARIES via a new `contains_word` helper, aligning this seam with the word-boundary policy already adopted by the same classifier's failure-signal pass (#4080) and the knowledge-pack relevance scorer (#4241). `unmerged`/`submerged` down-scope to the operational tier; a genuine whole-word `merged` still classifies as a durable completion, so no real completion changes disposition. The multi-word phrases (`opened pr`, `pull request`) cannot embed in a single word, so they remain substring checks. Internal surface only — `classify` signature/contract unchanged, no CLI/operator surface touched. New unit tests pin both the negative (`unmerged`/`submerged`) and positive (whole-word `merged`) contract; a qa-team gadugi scenario drives them outside-in. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
rysweet
added a commit
that referenced
this pull request
Jul 17, 2026
…ures (#893) (#4236) #4080 stopped an isolated per-intervention act() error from pinning the `overseer` meta-thread in "erroring", but a genuine `run_cycle()` failure whose cause is transient and external (an upstream 5xx, a timeout, a connection reset, a rate-limit) still latched "erroring" until a later clean tick — degrading the outer OODA stewardship loop even though nothing is actually wrong with Simard. This adds a bounded self-healing rung, purely additive to the post-#4080 baseline: - `wiring::is_transient` — a pure, fail-closed classifier over `OverseerError`. Only a `Capability` error whose detail names a known-retryable upstream fault is transient; every unknown detail and every non-`Capability` variant is fatal (SR-1). - `OverseerTickReport::transient_cycle_failure` — additive `#[serde(default)]` flag set only when a `cycle_failed` tick's error was transient (never on a panic). Legacy feed records deserialize to the conservative `false`; SCHEMA_VERSION is unchanged. - `OverseerThreadStatus::overseer_meta` — extended to consume the report plus a running consecutive-transient count and a configured ceiling: a transient failure within the ceiling maps to a self-clearing "backoff" (one cadence, no erroring counter); beyond the ceiling it escalates to "erroring" so a hard-down dependency cannot hide behind an infinite backoff (SR-2); fatal and panicked failures still map straight to "erroring". - `config::overseer_transient_backoff_ceiling{,_from}` — fail-safe env-injectable ceiling resolver (`SIMARD_OVERSEER_TRANSIENT_BACKOFF_CEILING`, default 3, always >= 1). - daemon loop owns the consecutive-transient counter across ticks (reset on a completed tick, incremented on a transient failure, unchanged on a fatal one). Docs (self-healing reference, activity-feed reference, index) move from "design — not yet implemented" to shipped. Structured tracing + the tick log carry the new `transient` signal; no println!/print!. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Improves cognition (distillation fact-yield + reasoner reliability, the standing
perpetual goal's axes) by fixing a false-positive failure-signal in the
episode-ingestion classifier (
src/memory_consolidation/classifier.rs, issue#2327).
Problem
Rule 1 (the failure override) detected failure/error episodes with a naive
substring scan over
{error, failed, failure, panic, exception}. Substringmatching mis-fired on look-alikes:
exceptional⊃exceptionhispanic⊃panicterror/mirror⊃errorThese benign/positive episodes were promoted to full importance (0.9) as
ActionFailure, polluting episodic memory and flowing into distillation asphantom failure facts — degrading fact-yield quality and reasoner
reliability.
Fix
Two complementary word-boundary passes (no bare substring), so coincidental
substrings never fire:
{error, fail, failure, panic, panick, exception}plus benign inflectional suffixes("", s, es, d, ed, ing). Genuine inflections still fire (errors,failed,failing,panicked,panicking,exceptions,failures); derivational look-alikes(
exceptional) do not.ParseError,IoError,NullPointerException, … whose stem sits at the end of a delimiter-lesscompound the prefix rule can't see. The capitalised segment distinguishes a
real type name from the lowercase look-alike
terror.Net: keeps every genuine failure word the substring scan caught plus compound
error/exception type identifiers, while dropping the false positives.
Merge-ready evidence
1. qa-team scenario (written, validated, run)
tests/qa-scenarios/classifier-failure-signal-fidelity.yaml.gadugi-test validate -f …→✓ Scenario "classifier-failure-signal-fidelity" is valid(1 valid, 0 invalid).gadugi-test run -s classifier-failure-signal-fidelity→✓ Passed: 1 / Failed: 0 / All tests passed!(drivescargo test --locked --lib memory_consolidation::classifierand the fullmemory_consolidation::suite).2. Docs updated (user-facing surface)
docs/reference/episode-ingestion-classifier.md— Rule 1 rewritten to describe word-boundary + inflection + compound-type-name matching.docs/architecture/episode-ingestion-policy.md— Rule 1 updated to match.classifier.rsupdated.3. Quality audit (SEEK→VALIDATE→FIX cycles, ended clean)
type identifiers (
ParseError,NullPointerException) missed by the initialprefix-only matcher. Fixed by adding the case-sensitive compound-type-name
pass + tests.
lowercase look-alikes (
terror) stay excluded; call sites pass original-casecontent;
content_lcnot dead; no new false positives; tokenization edgecases (empty tokens, unicode, plural stripping) safe. No issues.
correctness/security findings remaining.
4. CI-equivalent gates green (0 failures)
cargo fmt --all -- --check✓,cargo clippy --release --no-deps -- -D warnings✓.cargo test --release --lib -- cognitive_memory bootstrap memory_ipc memory_consolidation✓,cargo clippy --all-targets --all-features --locked -- -D warnings✓ →all push-stage gates passed.memory_consolidation::tests pass, no regressions.6. Focused diff
4 files:
classifier.rs(fix),classifier_tests.rs(tests), 2 docs, 1 scenario. No unrelated edits.Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com