Prefer canonical concept plurals over learned aliases in Reader7.align

#10
by emberian - opened

Reader7.align can lose the words "number of" from a counting question, because a learned concept alias is tried before the plain plural of the concept. This patch tries the plural first. It is a reorder of two existing branches, ten lines changed in workbench/swarm/reader_v7.py, plus eight portable tests.

What goes wrong

The exported aliases contain ('number of albums', 'C') -> 'album', and nothing else maps to album. The training question "Which number of albums dates not published in 1998?" has the program FindAll, FilterYear 1998 (not equal), FilterConcept album, Count. When align looks for the concept album, the word "album" does not occur in "albums", so the alias branch runs first and matches the longer phrase "number of albums". The concept span becomes [6,22) and the "number of" cue is replaced along with it. What is left of the question is which X1.

So a real counting question teaches the outer wording which X1 for Count. A second training row then lands on the same wording: "Which calendar month follows March?" has the program Find March, Relate (followed by), FilterConcept calendar month, Count, with stored answer 1. That row asks for a month and its program counts months. It is a question/program mismatch in its own right. Two rows (zero-based indices into the training JSON, SHA-256 e9fbe4c1cdf207aac83ae0d5e4a1a53a9965a2b13b403de699ca6d5dae6e4510):

  • 9630, "Which calendar month follows March?"
  • 32261, "Which number of albums dates not published in 1998?"

Together they give which X1 a Count support of 2 at level 0, which meets the outer-replication requirement. At level 1 only the album row still aligns (support 1). Because "number" is not in the operator or keep sets, the existing alias operator guards do not protect this phrase. The answer-kind model then treats the Count reading as legitimate for questions like the March one.

What the patch does

In Reader7.align, the existing canonical-plural branch (t+"s", t+"es", y to ies) now runs before the learned-alias branch. For the album row the concept span becomes [16,22) ("albums"), and the outer wording is which number of X1 at both levels. The March row is then the only contribution to which X1, so Count support there is 1 and the existing replication requirement rejects it.

The rule is general: explicit canonical morphology is preferred over a learned phrase that can absorb extra words. It does not name Count, "which", either row, or any answer.

What it does not change

No answer arbitration, no support or replication thresholds, no form model or form rules, no alias learning. Aliases are still used whenever the canonical form and its plurals are not found. The PLURAL switch still turns the plural branch off, and then behaviour is the old behaviour (tested).

Tests

tests/reader/test_canonical_plural_first.py, eight tests, standard library only. It imports workbench/swarm and does not build the KQA/Engine or load corpora. Run from the repository root:

python3 -B tests/reader/test_canonical_plural_first.py
  • On the unpatched 10.1 source (reader_v7.py SHA-256 f37f687e8190f08ff862738543790d6bcd56185a1a807ffdbd5cf8340da85354) exactly two fail: test_count_cue_not_consumed_by_album_alias (span is "number of albums", expected "albums") and test_exact_two_training_examples_no_longer_replicate_wrong_outer (Count support for which X1 is 2, expected 1). The other six pass on both versions.
  • On the patched source (reader_v7.py SHA-256 03e43e4e742b1bc6093fb67d00425ab47b679278fe1519b799b8cab91b90e57f) all eight pass.

The six that pass in both cases pin what must not move: a direct canonical phrase containing "number of", the existing plural branches, "how many" keeping Count, a non-concept literal still using its alias, PLURAL off preserving alias behaviour, and an unrelated alias staying usable.

What this does not establish, and what remains on your side

These are small source fixtures. I have not retrained, rebuilt the image, or run the specialist panels with this change, and I make no score claim. The learned tables in the shipped image were produced with the old order, so the album alias is still in them: the fix takes effect when the tables are re-exported with the patched source. That re-export needs your training data pipeline. Reader6.learn6 has its own alignment code without this plural branch; I did not change it, and you may want the same precedence there.

Background on how we found this, with the provenance replay: https://huggingface.co/psiloceyeben/oracle7-slim/discussions/5

This is a good find: a learned alias swallowing "number of" teaches which X1 as Count, and the March row shows how it leaks. It isn't in 10.3.1. It changes reader_v7.py, one of the files our release test hashes to carry 10.1's held-out results, so merging it voids that carry unless it's measured. We'll measure it on its own: every answer of the 10.1 cascade before and after on the 3,000 validation questions, with each changed answer published and a statement of which results a later seal can still carry. It will ship with that measurement, credited to you. #8 and #9 shipped in 10.3.1; there's a question for you on #9.

We have now run the isolated relearning/re-export that was still outstanding in our PR description. This supports your decision to measure the cascade separately; our result is a native-reader comparison, not a replacement for your 3,000-question test.

The pinned train/KB and original/patched Reader7 hashes, reproducible driver and export-delta checks are in the reader audit. Policies, answer-form rules, aliases and words were held fixed; the learned templates/shapes changed through actual relearning. The mislabeled March training row was neither relabeled nor removed.

With the same release native reader and the same one-second/10M-work/two-worker limits:

  • Original 100-question control: 36 correct in both images, no answer gain.
  • Targeted 16-question cohort: 2 correct / 8 unanswered / 6 expected declines becomes 10 correct / 6 expected declines.
  • Neither release cell had a wrong answer or execution error. Full receipt. Earlier debug deadline failures and the old extension's newly exposed exception remain in the separate first-wave receipt.

The export also exposed a useful remaining limitation. Newly replicated how many X1 percentage shapes can demand the unchanged generic C1 whose A1 is N1 fragment's Date interpretation for an inflation-rate literal such as 49.3. Our native compiler correctly rejects that Date literal; we repaired its handling to return an explicit unsupported-program refusal instead of an execution exception, without silently dropping the competing interpretation. Independent finite-store SQL supports a zero answer on this case, but our current nonempty-child rule still cannot deliver it. Typed scalar slots, unit context across composition boundaries and checked empty-count semantics remain follow-ups. The audit records the source rows and the limit of causal attribution: the exact first failing callback was not archived.

This is evidence for the plural precedence change on the named inputs, not a claim that all readers, Reader6 alignment, the whole Engine or a future release seal have been qualified.

Ready to merge
This branch is ready to get merged automatically.

Sign up or log in to comment