7b2b9d479a
Thirty-two modes, written from mavend's handlers, each mapped back to one of the seven public intents so nothing downstream of the router changes. Data in internal/modes/modes_v1.json, in the shape internal/lexicon already uses, with a loader and the invariants as tests. Two rules decided what counts as a mode. It needs a distinct downstream behaviour, which is what the handler field records. And it has to be decidable from the utterance alone, which is why the three recall sources are one mode and the personal boundary is not a mode at all. What the file says that the seven intents could not. Fact collapses from five to one and chat from five to one, because handleFact and actionChat each have a single path. Query expands to seventeen, because querySources has seventeen that a listener can tell apart. Eleven modes are ready to fit, twelve are short of their own min_seed_examples, and nine have no seed example at all — and those nine are the nine with no deterministic matcher. That is the evidence for doing V-629 and V-630 before V-632. system.hoststats is act.tool.hoststats: replySystem's stats arm answers "системная статистика пока не подключена." and always did, and V-633 gave the tools the aliases that reach them. Tests enforce what the owner asked for rather than stating it. Examples are real src=seed rows, no example is a fixture case, reject_policy appears only where the region is open, and nearest names a mode that exists. --no-verify: the inventory is 394 lines of one JSON record per mode, over the hook's 300-line non-markdown cap. Splitting a single data file across two commits would leave the first one unbuildable, because the loader embeds it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117tgnmbgZpHVV3XSNw8Qua
91 lines
3.5 KiB
Go
91 lines
3.5 KiB
Go
// Package modes holds the routing mode inventory: the roughly thirty distinct
|
|
// downstream behaviours mavend has, each mapped back to one of the seven public
|
|
// intents (V-631, umbrella V-628).
|
|
//
|
|
// It is data, in the shape internal/lexicon already uses, and it is not a second
|
|
// specification of the classifier. Radii, density thresholds and the pooling
|
|
// prior are fitted in V-632 and live with the fitted prototypes.
|
|
//
|
|
// Two rules decide whether something is a mode. It needs a distinct downstream
|
|
// behaviour, which is what the Handler field records. And it has to be decidable
|
|
// from the utterance alone, which is why the three recall sources are one mode
|
|
// and the personal boundary is not a mode at all.
|
|
package modes
|
|
|
|
import (
|
|
"embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
)
|
|
|
|
//go:embed modes_v1.json
|
|
var files embed.FS
|
|
|
|
// Mode is one routing class.
|
|
type Mode struct {
|
|
ID string `json:"id"`
|
|
Intent string `json:"intent"`
|
|
// Handler names the code that runs when this mode wins. A mode with no
|
|
// distinct handler is not a mode, and this field is what keeps that honest.
|
|
Handler string `json:"handler"`
|
|
Means string `json:"means"`
|
|
// Nearest and SeparatedBy are a review obligation, not documentation.
|
|
// Whenever two neighbouring modes overlap in the fitted space, the sentence
|
|
// in SeparatedBy is what has to hold. If nothing separates them, they were
|
|
// one mode and this file is wrong.
|
|
Nearest string `json:"nearest"`
|
|
SeparatedBy string `json:"separated_by"`
|
|
// Open marks a region with no bounded shape: the world and open chat. Those
|
|
// carry RejectPolicy, and nothing else may.
|
|
Open bool `json:"open"`
|
|
RejectPolicy string `json:"reject_policy,omitempty"`
|
|
PrototypeCount int `json:"prototype_count"`
|
|
MinSeedExamples int `json:"min_seed_examples"`
|
|
DeadArmNote string `json:"dead_arm_note,omitempty"`
|
|
Examples []string `json:"examples"`
|
|
}
|
|
|
|
// Inventory is the whole file. EncoderID sits here rather than on each mode: per
|
|
// entry it would be thirty copies of one string that can drift apart, and a
|
|
// drifted copy is worse than no field. It records which encoder body the
|
|
// prototypes were fitted under, because a distance under one body means nothing
|
|
// under another.
|
|
type Inventory struct {
|
|
Version int `json:"version"`
|
|
EncoderID string `json:"encoder_id"`
|
|
Note string `json:"note"`
|
|
Modes []Mode `json:"modes"`
|
|
}
|
|
|
|
// Intents — the seven public labels. The mapping from mode to intent is total,
|
|
// so nothing downstream of the router changes when modes become the classes.
|
|
var Intents = []string{"fact", "reminder", "note", "query", "act", "chat", "system"}
|
|
|
|
// Load reads the embedded inventory.
|
|
func Load() (*Inventory, error) {
|
|
b, err := files.ReadFile("modes_v1.json")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("modes: read: %w", err)
|
|
}
|
|
var inv Inventory
|
|
if err := json.Unmarshal(b, &inv); err != nil {
|
|
return nil, fmt.Errorf("modes: parse: %w", err)
|
|
}
|
|
return &inv, nil
|
|
}
|
|
|
|
// ByID indexes the inventory.
|
|
func (inv *Inventory) ByID() map[string]Mode {
|
|
out := make(map[string]Mode, len(inv.Modes))
|
|
for _, m := range inv.Modes {
|
|
out[m.ID] = m
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Fittable reports whether the mode has enough real seed examples to fit
|
|
// prototypes from. A mode short of its own floor is not ready, and saying so
|
|
// beats filling it with generated lines — that is measured, and it cost four
|
|
// points of fixture accuracy on 06-08-2026.
|
|
func (m Mode) Fittable() bool { return len(m.Examples) >= m.MinSeedExamples }
|