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
This commit is contained in:
+16
-3
@@ -222,11 +222,23 @@ func runProcess(ctx context.Context, argv []string) (string, error) {
|
||||
// router's default prefix logic over the current names. The interface's Match
|
||||
// has no ctx, so it queries with a background context — an in-process sqlite
|
||||
// read on the daemon.
|
||||
type Matcher struct{ api API }
|
||||
// Aliases are spoken phrases per tool name, wired from the deployment config so
|
||||
// a Russian utterance can reach an English tool name. They are not stored on the
|
||||
// tool row: an ad-hoc tool enabled through /tools has no aliases and needs none.
|
||||
type Matcher struct {
|
||||
api API
|
||||
aliases map[string][]string
|
||||
}
|
||||
|
||||
// NewMatcher builds a store-backed act matcher.
|
||||
func NewMatcher(api API) *Matcher { return &Matcher{api: api} }
|
||||
|
||||
// WithAliases returns the matcher carrying spoken aliases per tool name.
|
||||
func (m *Matcher) WithAliases(a map[string][]string) *Matcher {
|
||||
m.aliases = a
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Matcher) names() []string {
|
||||
ts, err := m.api.ListTools(context.Background(), "enabled")
|
||||
if err != nil {
|
||||
@@ -243,7 +255,8 @@ func (m *Matcher) names() []string {
|
||||
// Allowlist — the enabled verbs (for stage-0 grammar wiring / introspection).
|
||||
func (m *Matcher) Allowlist() []string { return m.names() }
|
||||
|
||||
// Match — longest-verb-first prefix match over the live enabled allowlist.
|
||||
// Match — longest-phrase-first prefix match over the live enabled allowlist and
|
||||
// its configured aliases.
|
||||
func (m *Matcher) Match(utterance string) (string, []string, bool) {
|
||||
return router.DefaultActMatcher{Fns: m.names()}.Match(utterance)
|
||||
return router.DefaultActMatcher{Fns: m.names(), Aliases: m.aliases}.Match(utterance)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user