Files
Maven/docs/evals/2026-08-06-russian-acts-reach-tools.md
claude e7ecce2859 a Russian act reaches a tool, and the seeds stop disagreeing (V-633)
Three tangled defects, fixed together because each one hid the others.

DefaultActMatcher matched an exact English prefix and internal/tool.Matcher
delegated straight to it, so no Russian utterance could reach a tool: 55 of the
69 lines in models/seeds/act.txt routed to IntentAct and fell to proposeGap.
Tools now carry spoken aliases from deploy/mavend.json, matched as exact leading
tokens, longest phrase first. Config data, not a stem pattern in code. The
comment claiming "the production matcher is fuzzy" was false and is gone.

Seven lines were exact duplicates inside models/seeds/query.txt, each one a
second identical vector double-weighting its region.

"как дела у сервера" carried both a query and a system label. It leaves
system.txt, because replySystem's stats arm answers "системная статистика пока
не подключена." and always did. The mode inventory records that shape as
act.tool.hoststats rather than a system mode.

Fixture unchanged at 69/91, and it cannot see any of this: no host-stat case and
no Russian act in it. TestActMatcherAliases is the coverage.
docs/evals/2026-08-06-russian-acts-reach-tools.md has the numbers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117tgnmbgZpHVV3XSNw8Qua
2026-08-06 18:09:11 +04:00

3.9 KiB
Raw Permalink Blame History

Russian acts reach tools

06-08-2026. V-633. Measured with TestONNXBaseline, 91-case RU routing fixture, classifier plus the ONNX embedder. No LLM arm in this run.

What was wrong

Three defects, tangled enough that fixing one alone would have looked like progress.

No Russian utterance could reach a tool. DefaultActMatcher in internal/router/slots.go matched an exact English prefix, and internal/tool.Matcher delegated straight to it. Its comment claimed "the production matcher is fuzzy, this is the scaffold floor". There is no other matcher, and DefaultGrammars is the only place Slots.Fn is set at stage 0, so the floor was the ceiling. Measured with a throwaway matcher test over the seeds:

"покажи статус nginx"    ok=false   "restart nginx"  ok=true fn=restart
"сколько места на диске" ok=false   "disk"           ok=true fn=disk
"свободная память"       ok=false   "uptime"         ok=true fn=uptime
"перезагрузи роутер"     ok=false

55 of the 69 lines in models/seeds/act.txt routed to IntentAct and then fell to proposeGap. Praxis was never affected: PraxisGrammars fills Slots.Fn itself.

Seven lines were duplicated inside models/seeds/query.txt. A duplicate is a second identical vector, so it double-weights its region in nearest-neighbour scoring.

сколько человек дома
кто сейчас дома
какая загрузка процессора
сколько свободного места на диске
какой ip адрес у сервера
какая версия софта
сколько оперативной памяти свободно

как дела у сервера carried two labels, in query.txt:13 and system.txt:9. One string, two identical vectors, disagreeing about the answer.

The change

Tools carry spoken aliases as config data, in deploy/mavend.json. They are not a Russian stem pattern in code, which CLAUDE.md forbids. They are not on the tool row either. An ad-hoc tool enabled through /tools has no aliases and needs none.

Aliases and names compete in one table, longest phrase first, so "перезагрузи контейнер" beats "перезагрузи" and "docker-restart" is not shadowed by "restart". Matching is on exact leading tokens rather than lemmas. перезагрузи роутер is a command and перезагрузил роутер is a fact, and a lemma cannot tell the two apart. That is the trap cmd/mavend/quiet_toggle.go documents for говори.

The seven duplicates are gone, and как дела у сервера stays in query.txt only. It left system.txt because system cannot answer it: replySystem's память/загрузк/аптайм arm returns "системная статистика пока не подключена." and always did. That arm is a stub, not a mode, so the mode inventory now lists the shape as act.tool.hoststats.

Result

69/91, 75.8% full, unchanged. Clarify counts unchanged at 0 false and 8 missed.

Nothing moved, and that is the honest number. The fixture holds no host-stat case and no Russian act that reaches a tool, so it cannot see either fix. The new coverage is TestActMatcherAliases, which asserts the twelve utterances above plus the two refusals.

What this does not fix

Argument quality. статус sshd reaches systemctl status sshd, but логи nginx reaches journalctl -n 50 -u nginx only because the tool's argv prefix ends in -u. An alias whose remainder is a Russian noun ("перезагрузи роутер") hands systemctl restart роутер a target that does not exist. Free text still reaches an argv, which is the resolution rule the ecosystem contract states for Hexis and not yet true here.

The fixture cannot measure any of this. That is the observability gap V-629 is for.