Only a literal pattern may take the personal boundary off a turn (V-666)

Naming a destination takes the guessing query sources off a turn, and the
personal boundary is one of them. Every other guesser costs an answer when it
is wrongly dropped. This one costs the rule that a question about him never
reaches an upstream engine.

Three deciders name a destination now and two of them infer it: the routing
heads and the resident model. Decision.SourceAnchored says a stage 0 grammar
read the words instead. queryWalk honours it for the source marked
boundary: true and for no other, so the rest of the table is unchanged.

Owner's call of 2026-08-09.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013ptwopxyo3Z2kwFckHkLvN
This commit is contained in:
2026-08-09 01:56:46 +04:00
parent 2512d686a1
commit c938148619
5 changed files with 84 additions and 21 deletions
+15 -7
View File
@@ -510,13 +510,21 @@ they look rather than guess.
**The personal boundary is the one exception and it is deliberate.** It guesses, **The personal boundary is the one exception and it is deliberate.** It guesses,
so naming `SourceWorld` drops it. That is what stops it answering "кто такой so naming `SourceWorld` drops it. That is what stops it answering "кто такой
Линус Торвальдс?" with "не нашла у тебя такой записи", which it did on Линус Торвальдс?" with "не нашла у тебя такой записи", which it did on
2026-08-07. The cost is that a destination a model wrote can now take the 2026-08-07. `TestNamingRecallKeepsTheBoundary` pins the other half: naming
boundary off a turn. A question about him that the model calls `world` reaches `SourceRecall` keeps the boundary in front of the world.
SearXNG, where today the boundary stops it. Only the utterance leaves the box,
never his notes or history, so this widens what is asked and not what is sent. **Only a stage 0 grammar may drop it** (owner's call, 09-08-2026, V-666). The
`TestNamingRecallKeepsTheBoundary` pins the other half: naming `SourceRecall` question of who is allowed to was open until then. Three deciders name a
keeps the boundary in front of the world. Whether a model may drop it at all is destination and two of them infer it: the routing heads and the resident model.
the owner's call and has not been made. An inferred `SourceWorld` on a question about him would reach SearXNG, and that
widens what is asked rather than costing a local answer. So `Decision.SourceAnchored`
carries the provenance. It is a field and not `Stage == 0`. Stage 0 also means
confidence 1.0 and an anchored claim band, and one of those could stop implying
the others. `queryWalk` reads it for the source marked `boundary: true` and for
no other. So every other guesser still comes off the turn, whoever named the
destination. `TestOnlyAGrammarMayDropTheBoundary` pins both directions.
`definitionQueryPattern` claims "кто такой X", so the 2026-08-07 case is still
anchored and still answered.
What comes out is only the sources that **guess**. Those decide a turn is theirs by What comes out is only the sources that **guess**. Those decide a turn is theirs by
cosine against frozen seeds, then answer whatever they claimed. They hold no table cosine against frozen seeds, then answer whatever they claimed. They hold no table
+18 -4
View File
@@ -79,6 +79,15 @@ type querySource struct {
// not named do not get to try. The lookups still run, because a named // not named do not get to try. The lookups still run, because a named
// destination is evidence and not a promise. // destination is evidence and not a promise.
guesses bool guesses bool
// boundary — dropping this source widens what leaves the box, so only a
// literal pattern may do it (V-666, owner's call of 2026-08-09).
//
// Every other guesser costs an answer when it is wrongly taken off a turn.
// This one costs the rule that a question about him never reaches an
// upstream engine. A grammar read the words to name a destination. A model
// and a softmax both inferred one, and neither may spend that.
boundary bool
} }
// querySources is the ordered chain actionQuery walks; first source to claim // querySources is the ordered chain actionQuery walks; first source to claim
@@ -156,7 +165,7 @@ var querySources = []querySource{
// below answers from the world's. A question about him that got this far // below answers from the world's. A question about him that got this far
// has no answer in his data, and no outside source can supply one, so this // has no answer in his data, and no outside source can supply one, so this
// stops the walk rather than let the encyclopedia and the model guess. // stops the walk rather than let the encyclopedia and the model guess.
{name: "personal", answer: (*reactiveHandler).queryPersonal, dest: router.SourceRecall, guesses: true}, {name: "personal", answer: (*reactiveHandler).queryPersonal, dest: router.SourceRecall, guesses: true, boundary: true},
// The world, read live. Owner's ruling of 2026-08-02: a metasearch hit beats // The world, read live. Owner's ruling of 2026-08-02: a metasearch hit beats
// a frozen ZIM, so SearXNG asks before Kiwix does. Nothing of his is at // a frozen ZIM, so SearXNG asks before Kiwix does. Nothing of his is at
// stake by this point — the boundary above already stopped every question // stake by this point — the boundary above already stopped every question
@@ -198,12 +207,17 @@ var querySources = []querySource{
// No destination named ⇒ the table exactly as written, which is what shipped // No destination named ⇒ the table exactly as written, which is what shipped
// before the field existed. That is the floor. The classifier arm names // before the field existed. That is the floor. The classifier arm names
// nothing, so a box whose model is down routes queries the way it always did. // nothing, so a box whose model is down routes queries the way it always did.
func queryWalk(dest router.Source) (walk, skipped []querySource) { // The personal boundary is the one exception, and anchored is what buys it
// (V-666). A grammar matched a literal pattern to name the destination. The
// routing heads and the resident model inferred one, and an inferred SourceWorld
// takes the boundary off a question about him. That widens what is asked
// upstream rather than costing a local answer, so those two keep it.
func queryWalk(dest router.Source, anchored bool) (walk, skipped []querySource) {
if dest == router.SourceUnknown { if dest == router.SourceUnknown {
return querySources, nil return querySources, nil
} }
for _, s := range querySources { for _, s := range querySources {
if s.guesses && s.dest != dest { if s.guesses && s.dest != dest && (anchored || !s.boundary) {
skipped = append(skipped, s) skipped = append(skipped, s)
continue continue
} }
@@ -219,7 +233,7 @@ func (h *reactiveHandler) actionQuery(ctx context.Context, dec router.Decision)
// (V-564). Finish names everyone below the winner. // (V-564). Finish names everyone below the winner.
decision.Expect(ctx, decision.StageQuery, querySourceNames()) decision.Expect(ctx, decision.StageQuery, querySourceNames())
rec := decision.From(ctx) rec := decision.From(ctx)
walk, skipped := queryWalk(dec.Source) walk, skipped := queryWalk(dec.Source, dec.SourceAnchored)
for _, src := range skipped { for _, src := range skipped {
rec.Note(decision.Claim{ rec.Note(decision.Claim{
Stage: decision.StageQuery, Claimant: src.name, Outcome: decision.NeverAsked, Stage: decision.StageQuery, Claimant: src.name, Outcome: decision.NeverAsked,
+32 -10
View File
@@ -10,7 +10,7 @@ import (
// whose model is down names nothing, and naming nothing has to walk the chain // whose model is down names nothing, and naming nothing has to walk the chain
// the way it walked before the field existed. // the way it walked before the field existed.
func TestNoDestinationWalksTheWholeChain(t *testing.T) { func TestNoDestinationWalksTheWholeChain(t *testing.T) {
walk, skipped := queryWalk(router.SourceUnknown) walk, skipped := queryWalk(router.SourceUnknown, false)
if len(skipped) != 0 { if len(skipped) != 0 {
t.Errorf("skipped %d sources with no destination named, want none", len(skipped)) t.Errorf("skipped %d sources with no destination named, want none", len(skipped))
} }
@@ -32,15 +32,16 @@ func TestANamedDestinationSilencesTheOtherGuessers(t *testing.T) {
dest router.Source dest router.Source
utterance string utterance string
silenced string silenced string
anchored bool // a stage 0 grammar named the destination
}{ }{
{router.SourceWorld, "что такое TCP?", "weather"}, {router.SourceWorld, "что такое TCP?", "weather", true},
{router.SourceWorld, "сколько будет 17 на 23?", "weather"}, {router.SourceWorld, "сколько будет 17 на 23?", "weather", true},
{router.SourceWorld, "кто такой Линус Торвальдс?", "personal"}, {router.SourceWorld, "кто такой Линус Торвальдс?", "personal", true},
{router.SourceRecall, "какой у меня любимый язык?", "feeds"}, {router.SourceRecall, "какой у меня любимый язык?", "feeds", false},
{router.SourceCalendar, "что в календаре на завтра?", "weather"}, {router.SourceCalendar, "что в календаре на завтра?", "weather", true},
} }
for _, c := range cases { for _, c := range cases {
walk, skipped := queryWalk(c.dest) walk, skipped := queryWalk(c.dest, c.anchored)
if inWalk(walk, c.silenced) { if inWalk(walk, c.silenced) {
t.Errorf("%q named %q: %q is still asked", c.utterance, c.dest, c.silenced) t.Errorf("%q named %q: %q is still asked", c.utterance, c.dest, c.silenced)
} }
@@ -56,7 +57,7 @@ func TestANamedDestinationSilencesTheOtherGuessers(t *testing.T) {
// data first, then the world", and a destination a model wrote must not be able // data first, then the world", and a destination a model wrote must not be able
// to reverse it. // to reverse it.
func TestNamingTheWorldStillReadsHisDataFirst(t *testing.T) { func TestNamingTheWorldStillReadsHisDataFirst(t *testing.T) {
walk, _ := queryWalk(router.SourceWorld) walk, _ := queryWalk(router.SourceWorld, true)
for _, look := range []string{"fact-by-key", "embed", "memory", "notes"} { for _, look := range []string{"fact-by-key", "embed", "memory", "notes"} {
if !inWalk(walk, look) { if !inWalk(walk, look) {
t.Errorf("%q was dropped; only the sources that guess may be dropped", look) t.Errorf("%q was dropped; only the sources that guess may be dropped", look)
@@ -74,7 +75,7 @@ func TestNamingTheWorldStillReadsHisDataFirst(t *testing.T) {
// makes "какой у меня любимый язык?" answer "не нашла у тебя такой записи" // makes "какой у меня любимый язык?" answer "не нашла у тебя такой записи"
// rather than reaching SearXNG once nothing local had it. // rather than reaching SearXNG once nothing local had it.
func TestNamingRecallKeepsTheBoundary(t *testing.T) { func TestNamingRecallKeepsTheBoundary(t *testing.T) {
walk, _ := queryWalk(router.SourceRecall) walk, _ := queryWalk(router.SourceRecall, true)
if !inWalk(walk, "personal") { if !inWalk(walk, "personal") {
t.Fatal("the personal boundary was skipped on a turn named for his own data") t.Fatal("the personal boundary was skipped on a turn named for his own data")
} }
@@ -83,12 +84,33 @@ func TestNamingRecallKeepsTheBoundary(t *testing.T) {
} }
} }
// The owner's call of 2026-08-09 (V-666): only a stage 0 grammar may take the
// personal boundary off a turn. The routing heads and the resident model both
// name a destination by inference, and an inferred SourceWorld would send a
// question about him upstream. Every other guesser still goes.
func TestOnlyAGrammarMayDropTheBoundary(t *testing.T) {
walk, skipped := queryWalk(router.SourceWorld, false)
if !inWalk(walk, "personal") {
t.Error("an inferred destination took the boundary off the turn")
}
if !inWalk(skipped, "weather") {
t.Error("weather is still asked; the rule covers the boundary alone")
}
if posOf(walk, "personal") > posOf(walk, "search") {
t.Error("the boundary no longer sits in front of the world")
}
if anchored, _ := queryWalk(router.SourceWorld, true); inWalk(anchored, "personal") {
t.Error(`a grammar named the world and the boundary stayed: ` +
`"кто такой Линус Торвальдс?" is answered "не нашла у тебя такой записи" again`)
}
}
// Whatever the destination, the walk is a subsequence of the table. Every // Whatever the destination, the walk is a subsequence of the table. Every
// comment on that table argues an order between two sources, and none of those // comment on that table argues an order between two sources, and none of those
// reasons is about this field. // reasons is about this field.
func TestTheWalkNeverReordersTheTable(t *testing.T) { func TestTheWalkNeverReordersTheTable(t *testing.T) {
for _, dest := range append([]router.Source{router.SourceUnknown}, router.Sources...) { for _, dest := range append([]router.Source{router.SourceUnknown}, router.Sources...) {
walk, skipped := queryWalk(dest) walk, skipped := queryWalk(dest, true)
if len(walk)+len(skipped) != len(querySources) { if len(walk)+len(skipped) != len(querySources) {
t.Errorf("%q: %d walked + %d skipped, want %d", dest, len(walk), len(skipped), len(querySources)) t.Errorf("%q: %d walked + %d skipped, want %d", dest, len(walk), len(skipped), len(querySources))
} }
+15
View File
@@ -111,6 +111,21 @@ type Decision struct {
// before this field existed. See source.go for why it is twelve values. // before this field existed. See source.go for why it is twelve values.
Source Source Source Source
// SourceAnchored — a stage 0 grammar named that destination, matching a
// literal pattern to do it. Only the router sets this, and only there.
//
// It exists because one thing downstream is not reversible by evidence
// (V-666). Naming a destination normally takes guessing sources off a turn,
// and one of those is the personal boundary, which is what stops a question
// about him from reaching the world. A grammar that read "что такое X" may
// take it off. A model or a softmax may not, because a wrong destination
// there widens what leaves the box rather than costing an answer.
//
// Read Stage instead and the two decisions get coupled: stage 0 also means
// confidence 1.0 and an anchored claim band, and a later cascade change
// could make one true where the other is not.
SourceAnchored bool
// Continued — this decision was rebuilt from the previous turn rather // Continued — this decision was rebuilt from the previous turn rather
// than routed, because the utterance was an ellipsis ("а завтра?"). // than routed, because the utterance was an ellipsis ("а завтра?").
// Handlers use it to know that Slots.Text is the PREVIOUS turn's topic // Handlers use it to know that Slots.Text is the PREVIOUS turn's topic
+4
View File
@@ -98,6 +98,10 @@ func (r *Router) Route(ctx context.Context, utterance string, now time.Time) (De
continue // grammar matched shape but not content → fall through continue // grammar matched shape but not content → fall through
} }
d.Utterance = utterance d.Utterance = utterance
// A literal pattern named that destination, which is the one provenance
// allowed to take the personal boundary off a turn (V-666). Set here and
// nowhere else, so no other arm of the cascade can claim it.
d.SourceAnchored = d.Source != SourceUnknown
// The grammar decided the intent; the extractor fills the slots it did // The grammar decided the intent; the extractor fills the slots it did
// not match (V-572). See fillMatchedSlots for why every grammar gets it. // not match (V-572). See fillMatchedSlots for why every grammar gets it.
r.fillMatchedSlots(ctx, &d, now) r.fillMatchedSlots(ctx, &d, now)