Baseline
10 / 11 MEETS
nescio-mind · shipped feature · issue #6
Teaching the reasoner to see and say when two answers are equally likely — the fix for F1 (ambiguity discarded before voting) and F3 (ambiguity never surfaced).
Baseline
10 / 11 MEETS
Result
voting scenario now MEETS
Locus
cataloger + neuron only
Sub-systems
unchanged
The problem · F1
When two categories share an identical feature profile, BayesianReasoner computes exactly what you'd hope — a 50/50 posterior. But Cataloger._normalize_one keeps only each sub-system's single top answer, so the tie is gone before the cataloger ever votes. The system built to prefer "reporting competing candidates over picking one arbitrarily" cannot represent the most basic competition of all.
What Bayesian computes
A genuine tie. Both live in the distribution.
What the cataloger keeps
cheetah discarded. Result: 1 candidate, share 1.00, ambiguity 0.00.
The fix · distributed mass voting
The distributions already exist in the sub-system outputs; the cataloger just stops discarding them. Each sub-system casts votes across its pruned candidate set, weighted by its own distribution. Two genuinely-close categories then accumulate comparable weight, and the existing ambiguity = runner_share / top_share formula finally sees real data.
Worked example
| Stage | leopard | cheetah | Note |
|---|---|---|---|
| Bayesian posterior | 0.50 | 0.50 | identical profiles |
| Pattern similarity | 0.90 | 0.90 | both kept (≥ 0.5 × top) |
| Distributed tally | 1.40 | 1.40 | summed weights |
| Composite share | 0.50 | 0.50 | — |
| ambiguity = runner ÷ top | 0.50 / 0.50 = 1.00 | ≥ 0.70 → flag | |
| result | ambiguous: True · 2 candidates | surfaced on the return path | |
A clear winner behaves as before: its runner-up prunes away (below 0.5 × top), leaving one candidate and ambiguous: False. The change only fires on a genuine near-tie.
Surfacing it · F3
Fixing the cataloger is invisible unless the neuron surfaces it. Before this fix, ambiguous was set only in the uncertain-path helper, so a confident near-tie returned at step 6 with no signal at all. Now ambiguous is a single composite-level field, carried out of every exit.
| Return path | ambiguous | candidates |
|---|---|---|
| Fast-path cache (step 1) | False | — (a confident cache hit is unambiguous) |
| Confident (step 6) | composite["ambiguous"] | composite candidates |
| Routing delegation (step 7) | external["ambiguous"] | propagated from the expert |
| Honest uncertainty (step 8) | composite["ambiguous"] | composite candidates |
Named constants
The thing this project is rightly wary of — unmotivated magic numbers. Each lives in one place as a named parameter, tested on both sides of its boundary.
candidate_keep_ratio
0.5
A candidate must be ≥ half the top's weight (per sub-system) to count. A clear winner prunes to one; a near-tie keeps both.
max_candidates_per_subsystem
3
Caps distribution-tail noise beyond the real contenders.
ambiguity_flag_threshold
0.7
Runner-up within 70% of the winner's share reads as genuinely competing. Makes explicit a value already implicit in the code.
How we know it works
Intent scenarios
Cataloger unit tests
Decisions & boundaries
Shipped
Out of scope