Skip to main content

wm_tools/
nlu.rs

1//! Embedding-based NLU router for the `wm` meta-tool.
2//!
3//! Replaces the 450-line keyword `if-else` chain with a data-driven TF-IDF
4//! cosine similarity router. Each tool has a weighted keyword profile. Input
5//! text is tokenized and compared against all profiles using cosine similarity.
6//!
7//! Advantages over the keyword chain:
8//! - Scales to hundreds of tools without code changes (just add profiles)
9//! - Handles partial matches and multi-word queries naturally
10//! - Confidence score reflects actual semantic overlap, not arbitrary 0.9/1.0
11//! - No ordering dependency — all profiles scored independently
12
13use ahash::AHashMap;
14
15/// A tool routing profile with weighted keywords.
16#[derive(Debug, Clone)]
17pub struct ToolProfile {
18    pub tool_name: &'static str,
19    /// Weighted keywords — (term, weight) pairs.
20    /// Multi-word phrases are split into individual tokens.
21    pub keywords: &'static [(&'static str, f64)],
22}
23
24/// All tool profiles, ordered roughly by specificity (most specific first
25/// for tie-breaking, though cosine similarity makes this less critical).
26pub static TOOL_PROFILES: &[ToolProfile] = &[
27    // ── Memory operations ──────────────────────────────────────────
28    ToolProfile {
29        tool_name: "memory.create",
30        keywords: &[
31            ("remember", 7.0),
32            ("store", 7.0),
33            ("save", 5.0),
34            ("memorize", 5.0),
35            ("record", 2.0),
36            ("persist", 2.0),
37            ("capture", 1.5),
38        ],
39    },
40    ToolProfile {
41        tool_name: "memory.read",
42        keywords: &[
43            ("read", 2.5),
44            ("fetch", 2.5),
45            ("get", 1.5),
46            ("retrieve", 2.5),
47            ("memory", 1.0),
48            ("load", 2.0),
49            ("access", 1.5),
50            ("view", 1.5),
51            ("show", 1.5),
52        ],
53    },
54    ToolProfile {
55        tool_name: "memory.list",
56        keywords: &[
57            ("list", 3.0),
58            ("show", 2.0),
59            ("all", 1.5),
60            ("memories", 2.0),
61            ("browse", 2.0),
62            ("enumerate", 2.0),
63            ("display", 2.0),
64            ("view", 1.5),
65        ],
66    },
67    ToolProfile {
68        tool_name: "memory.delete",
69        keywords: &[
70            ("delete", 3.0),
71            ("remove", 2.5),
72            ("forget", 2.5),
73            ("erase", 2.5),
74            ("destroy", 2.0),
75            ("purge", 1.5),
76            ("drop", 2.0),
77            ("clear", 1.5),
78            ("discard", 2.0),
79        ],
80    },
81    ToolProfile {
82        tool_name: "memory.search",
83        keywords: &[
84            ("search", 3.5),
85            ("find", 2.0),
86            ("recall", 2.5),
87            ("query", 1.0),
88            ("lookup", 2.0),
89            ("fulltext", 2.5),
90            ("full-text", 2.5),
91            ("seek", 1.5),
92            ("locate", 1.5),
93            ("grep", 2.0),
94        ],
95    },
96    ToolProfile {
97        tool_name: "memory.chat",
98        keywords: &[
99            ("chat", 3.5),
100            ("conversational", 3.0),
101            ("converse", 2.5),
102            ("talk", 2.0),
103            ("ask", 2.0),
104            ("discuss", 2.0),
105            ("explore", 1.5),
106            ("browse", 1.5),
107            ("hybrid", 2.0),
108        ],
109    },
110    ToolProfile {
111        tool_name: "memory.vector.search",
112        keywords: &[
113            ("vector", 3.0),
114            ("embedding", 3.0),
115            ("similar", 2.5),
116            ("similarity", 3.0),
117            ("semantic", 2.5),
118            ("nearest", 2.0),
119            ("neighbors", 1.5),
120            ("cosine", 2.0),
121            ("ann", 2.0),
122            ("alike", 2.0),
123            ("like", 1.0),
124            ("close", 1.5),
125        ],
126    },
127    ToolProfile {
128        tool_name: "memory.query",
129        keywords: &[
130            ("query", 3.0),
131            ("filter", 2.5),
132            ("where", 1.5),
133            ("select", 2.0),
134            ("condition", 2.0),
135            ("criteria", 2.0),
136            ("match", 1.5),
137            ("search", 1.0),
138        ],
139    },
140    ToolProfile {
141        tool_name: "memory.hybrid_recall",
142        keywords: &[
143            ("hybrid", 3.0),
144            ("smart", 2.5),
145            ("combined", 2.5),
146            ("recall", 1.5),
147            ("intelligent", 2.0),
148            ("fusion", 2.0),
149        ],
150    },
151    ToolProfile {
152        tool_name: "memory.associate",
153        keywords: &[
154            ("associate", 3.0),
155            ("link", 2.5),
156            ("connect", 2.0),
157            ("relate", 2.5),
158            ("tie", 1.5),
159            ("bind", 1.5),
160        ],
161    },
162    ToolProfile {
163        tool_name: "memory.associations",
164        keywords: &[
165            ("associations", 3.0),
166            ("links", 2.5),
167            ("related", 2.5),
168            ("connections", 2.0),
169            ("edges", 2.0),
170            ("neighbors", 1.5),
171        ],
172    },
173    ToolProfile {
174        tool_name: "memory.associate_mine",
175        keywords: &[
176            ("mine", 4.0),
177            ("discover", 2.0),
178            ("associations", 1.5),
179            ("uncover", 2.0),
180            ("excavate", 2.0),
181        ],
182    },
183    ToolProfile {
184        tool_name: "memory.consolidate",
185        keywords: &[
186            ("consolidate", 3.0),
187            ("deduplicate", 3.0),
188            ("dedup", 3.0),
189            ("merge", 2.0),
190            ("duplicate", 2.0),
191            ("combine", 1.5),
192        ],
193    },
194    ToolProfile {
195        tool_name: "memory.decay",
196        keywords: &[
197            ("decay", 3.0),
198            ("age", 2.0),
199            ("expire", 2.5),
200            ("stale", 2.0),
201            ("rot", 1.5),
202            ("degrade", 2.0),
203        ],
204    },
205    ToolProfile {
206        tool_name: "memory.batch_read",
207        keywords: &[
208            ("batch", 3.0),
209            ("multiple", 2.5),
210            ("bulk", 2.5),
211            ("read", 1.0),
212            ("many", 2.0),
213            ("several", 1.5),
214        ],
215    },
216    ToolProfile {
217        tool_name: "memory.update",
218        keywords: &[
219            ("update", 5.0),
220            ("modify", 2.0),
221            ("change", 2.0),
222            ("edit", 2.0),
223            ("alter", 1.5),
224            ("revise", 1.5),
225            ("memory", 1.0),
226            ("amend", 1.5),
227            ("patch", 1.5),
228        ],
229    },
230    ToolProfile {
231        tool_name: "memory.tag",
232        keywords: &[
233            ("tag", 5.0),
234            ("label", 2.0),
235            ("retag", 3.0),
236            ("categorize", 2.0),
237            ("mark", 1.5),
238            ("add", 1.5),
239        ],
240    },
241    ToolProfile {
242        tool_name: "memory.stats",
243        keywords: &[
244            ("stats", 3.0),
245            ("statistics", 3.0),
246            ("summary", 2.0),
247            ("memory", 1.0),
248            ("galaxy", 1.5),
249            ("count", 1.0),
250        ],
251    },
252    ToolProfile {
253        tool_name: "memory.count",
254        keywords: &[
255            ("count", 3.0),
256            ("how", 1.5),
257            ("many", 2.0),
258            ("number", 2.5),
259            ("total", 2.0),
260            ("memories", 1.5),
261        ],
262    },
263    ToolProfile {
264        tool_name: "memory.tags",
265        keywords: &[
266            ("tags", 4.0),
267            ("labels", 2.0),
268            ("categories", 1.5),
269            ("list", 1.0),
270        ],
271    },
272    ToolProfile {
273        tool_name: "memory.nearby",
274        keywords: &[
275            ("nearby", 3.0),
276            ("near", 2.5),
277            ("close", 2.0),
278            ("spatial", 2.5),
279            ("proximity", 2.5),
280            ("surrounding", 2.0),
281            ("adjacent", 2.0),
282        ],
283    },
284    // ── Session ────────────────────────────────────────────────────
285    ToolProfile {
286        tool_name: "session.start",
287        keywords: &[
288            ("start", 3.0),
289            ("new", 2.5),
290            ("begin", 2.5),
291            ("open", 2.0),
292            ("session", 2.0),
293            ("create", 1.5),
294        ],
295    },
296    ToolProfile {
297        tool_name: "session.checkpoint",
298        keywords: &[
299            ("checkpoint", 3.0),
300            ("snapshot", 3.0),
301            ("save", 1.5),
302            ("point", 2.0),
303            ("marker", 2.5),
304        ],
305    },
306    ToolProfile {
307        tool_name: "session.recall",
308        keywords: &[
309            ("recall", 2.0),
310            ("session", 3.0),
311            ("history", 2.5),
312            ("replay", 2.5),
313            ("previous", 1.5),
314        ],
315    },
316    ToolProfile {
317        tool_name: "session.end",
318        keywords: &[
319            ("end", 3.0),
320            ("close", 2.5),
321            ("stop", 2.0),
322            ("finish", 2.5),
323            ("terminate", 2.5),
324            ("session", 1.5),
325        ],
326    },
327    ToolProfile {
328        tool_name: "session.list",
329        keywords: &[
330            ("list", 2.5),
331            ("show", 2.0),
332            ("all", 1.5),
333            ("sessions", 3.0),
334            ("history", 1.5),
335        ],
336    },
337    ToolProfile {
338        tool_name: "session.continuity",
339        keywords: &[
340            ("resume", 3.0),
341            ("continue", 2.5),
342            ("left", 2.0),
343            ("off", 1.5),
344            ("last", 2.0),
345            ("time", 1.5),
346            ("previous", 2.5),
347            ("pick", 2.0),
348            ("up", 1.5),
349            ("decide", 2.0),
350            ("recap", 2.5),
351            ("where", 1.5),
352            ("were", 1.5),
353            ("handoff", 1.5),
354            ("carry", 1.5),
355            ("over", 1.0),
356            ("session", 1.5),
357        ],
358    },
359    ToolProfile {
360        tool_name: "session.record",
361        keywords: &[
362            ("record", 3.0),
363            ("log", 2.5),
364            ("turn", 2.5),
365            ("note", 2.0),
366            ("decision", 2.0),
367            ("breakthrough", 2.0),
368            ("transcript", 1.5),
369            ("capture", 1.5),
370        ],
371    },
372    // ── Consciousness ──────────────────────────────────────────────
373    ToolProfile {
374        tool_name: "citta.status",
375        keywords: &[
376            ("citta", 3.0),
377            ("consciousness", 2.5),
378            ("status", 1.5),
379            ("vector", 2.0),
380            ("awareness", 2.0),
381        ],
382    },
383    ToolProfile {
384        tool_name: "citta.reflect",
385        keywords: &[
386            ("reflect", 5.0),
387            ("introspect", 3.0),
388            ("meditate", 2.5),
389            ("contemplate", 2.5),
390            ("self", 1.5),
391            ("examine", 1.5),
392        ],
393    },
394    ToolProfile {
395        tool_name: "citta.coherence",
396        keywords: &[
397            ("coherence", 3.0),
398            ("coherent", 3.0),
399            ("write", 1.5),
400            ("permitted", 2.5),
401            ("allowed", 2.0),
402            ("can", 1.0),
403        ],
404    },
405    ToolProfile {
406        tool_name: "dream.trigger",
407        keywords: &[
408            ("trigger", 3.0),
409            ("start", 1.5),
410            ("initiate", 2.5),
411            ("dream", 3.0),
412            ("begin", 1.5),
413            ("sleep", 1.5),
414        ],
415    },
416    ToolProfile {
417        tool_name: "dream.status",
418        keywords: &[
419            ("dream", 3.0),
420            ("status", 2.0),
421            ("cycle", 2.5),
422            ("sleep", 2.0),
423            ("phase", 2.0),
424        ],
425    },
426    // ── Tools management ───────────────────────────────────────────
427    ToolProfile {
428        tool_name: "tools.effectiveness_report",
429        keywords: &[
430            ("effectiveness", 4.0),
431            ("performance", 1.5),
432            ("report", 2.0),
433            ("efficiency", 2.0),
434        ],
435    },
436    ToolProfile {
437        tool_name: "tools.retire",
438        keywords: &[
439            ("retire", 3.0),
440            ("decommission", 3.0),
441            ("remove", 1.5),
442            ("disable", 2.0),
443            ("tool", 1.0),
444            ("sunset", 2.5),
445        ],
446    },
447    ToolProfile {
448        tool_name: "tools.list",
449        keywords: &[
450            ("tools", 3.0),
451            ("list", 2.0),
452            ("available", 2.0),
453            ("catalog", 2.5),
454            ("inventory", 2.0),
455            ("discover", 1.5),
456        ],
457    },
458    // ── Patterns ───────────────────────────────────────────────────
459    ToolProfile {
460        tool_name: "pattern.search",
461        keywords: &[
462            ("pattern", 4.0),
463            ("recurring", 3.0),
464            ("repeating", 2.5),
465            ("cycle", 2.0),
466            ("frequency", 2.0),
467            ("regularity", 2.0),
468        ],
469    },
470    ToolProfile {
471        tool_name: "salience.spotlight",
472        keywords: &[
473            ("salience", 4.0),
474            ("spotlight", 4.0),
475            ("important", 1.0),
476            ("prominent", 2.0),
477            ("notable", 2.0),
478            ("highlight", 2.0),
479        ],
480    },
481    ToolProfile {
482        tool_name: "serendipity.surface",
483        keywords: &[
484            ("serendipity", 3.0),
485            ("serendipit", 3.0),
486            ("unexpected", 2.5),
487            ("surprising", 2.5),
488            ("connection", 1.5),
489            ("surface", 1.5),
490        ],
491    },
492    // ── Constellation ──────────────────────────────────────────────
493    ToolProfile {
494        tool_name: "constellation.detect",
495        keywords: &[
496            ("constellation", 3.0),
497            ("detect", 4.0),
498            ("cluster", 3.0),
499            ("grouping", 2.0),
500            ("density", 2.0),
501        ],
502    },
503    ToolProfile {
504        tool_name: "constellation.list",
505        keywords: &[
506            ("constellation", 3.0),
507            ("list", 4.0),
508            ("show", 2.0),
509            ("clusters", 2.0),
510        ],
511    },
512    // ── Autonomous Cycles (Phase E) ────────────────────────────────
513    ToolProfile {
514        tool_name: "consolidation.connect",
515        keywords: &[
516            ("connect", 2.5),
517            ("disconnected", 3.0),
518            ("link", 2.0),
519            ("propose", 2.0),
520            ("connection", 2.0),
521            ("bridge", 2.0),
522            ("unlinked", 2.5),
523        ],
524    },
525    ToolProfile {
526        tool_name: "consolidation.compress",
527        keywords: &[
528            ("compress", 3.0),
529            ("merge", 2.5),
530            ("redundancy", 3.0),
531            ("overlapping", 2.5),
532            ("reduce", 2.0),
533            ("deduplicate", 2.0),
534        ],
535    },
536    ToolProfile {
537        tool_name: "emergence.scan",
538        keywords: &[
539            ("emergence", 3.0),
540            ("emerging", 3.0),
541            ("scan", 2.0),
542            ("tag", 1.5),
543            ("topic", 2.5),
544            ("trend", 2.5),
545            ("pattern", 1.5),
546        ],
547    },
548    ToolProfile {
549        tool_name: "retention.prune",
550        keywords: &[
551            ("prune", 3.0),
552            ("forget", 2.0),
553            ("ready", 2.0),
554            ("forgettable", 3.0),
555            ("retention", 2.5),
556            ("candidate", 2.0),
557            ("cleanup", 1.5),
558        ],
559    },
560    // ── Spiral Report (Phase F) ────────────────────────────────────
561    ToolProfile {
562        tool_name: "spiral.report",
563        keywords: &[
564            ("spiral", 3.0),
565            ("autonomy", 2.5),
566            ("circular", 2.5),
567            ("thinking", 2.0),
568            ("expansion", 2.5),
569            ("novelty", 3.0),
570            ("report", 1.5),
571        ],
572    },
573    // ── Galaxy ─────────────────────────────────────────────────────
574    ToolProfile {
575        tool_name: "galaxy.stats",
576        keywords: &[
577            ("galaxy", 3.0),
578            ("stats", 3.0),
579            ("count", 1.5),
580            ("overview", 2.0),
581        ],
582    },
583    ToolProfile {
584        tool_name: "galaxy.export",
585        keywords: &[
586            ("export", 3.0),
587            ("backup", 2.5),
588            ("dump", 2.0),
589            ("galaxy", 2.0),
590            ("save", 1.5),
591        ],
592    },
593    ToolProfile {
594        tool_name: "galaxy.import",
595        keywords: &[
596            ("import", 3.0),
597            ("restore", 3.0),
598            ("load", 2.0),
599            ("galaxy", 2.0),
600            ("ingest", 2.5),
601        ],
602    },
603    // ── Karma ──────────────────────────────────────────────────────
604    ToolProfile {
605        tool_name: "karma.report",
606        keywords: &[
607            ("karma", 3.0),
608            ("debt", 2.5),
609            ("ledger", 2.5),
610            ("balance", 2.0),
611            ("report", 1.5),
612        ],
613    },
614    ToolProfile {
615        tool_name: "karma.history",
616        keywords: &[
617            ("karma", 2.5),
618            ("history", 3.0),
619            ("log", 2.5),
620            ("entries", 2.5),
621            ("record", 1.5),
622            ("past", 2.0),
623        ],
624    },
625    ToolProfile {
626        tool_name: "karma.clear",
627        keywords: &[
628            ("clear", 3.0),
629            ("wipe", 3.0),
630            ("reset", 2.5),
631            ("purge", 2.5),
632            ("karma", 2.0),
633            ("clean", 2.0),
634        ],
635    },
636    // ── Dharma ─────────────────────────────────────────────────────
637    ToolProfile {
638        tool_name: "dharma.status",
639        keywords: &[
640            ("dharma", 3.0),
641            ("governance", 2.5),
642            ("ethics", 2.5),
643            ("status", 1.5),
644            ("rules", 1.0),
645        ],
646    },
647    ToolProfile {
648        tool_name: "dharma.rules",
649        keywords: &[
650            ("dharma", 2.5),
651            ("rules", 3.0),
652            ("governance", 2.0),
653            ("ethics", 2.0),
654            ("laws", 2.5),
655            ("principles", 2.0),
656        ],
657    },
658    ToolProfile {
659        tool_name: "dharma.audit",
660        keywords: &[
661            ("dharma", 2.0),
662            ("audit", 3.0),
663            ("governance", 2.0),
664            ("ethics", 1.5),
665            ("inspect", 2.5),
666            ("review", 2.0),
667        ],
668    },
669    ToolProfile {
670        tool_name: "dharma.profiles",
671        keywords: &[
672            ("dharma", 2.0),
673            ("profiles", 3.0),
674            ("governance", 1.5),
675            ("ethics", 1.5),
676            ("modes", 2.0),
677            ("configurations", 2.0),
678        ],
679    },
680    // ── Harmony / Substrate ────────────────────────────────────────
681    ToolProfile {
682        tool_name: "harmony.vector",
683        keywords: &[
684            ("harmony", 3.0),
685            ("substrate", 3.0),
686            ("hardware", 2.5),
687            ("resource", 2.0),
688            ("cpu", 2.5),
689            ("memory", 1.5),
690            ("pressure", 2.5),
691            ("thermal", 2.5),
692            ("battery", 2.5),
693            ("system", 1.0),
694            ("vector", 2.0),
695            ("status", 1.5),
696        ],
697    },
698    ToolProfile {
699        tool_name: "harmony.history",
700        keywords: &[
701            ("harmony", 2.5),
702            ("history", 3.0),
703            ("substrate", 2.0),
704            ("hardware", 2.0),
705            ("resource", 1.5),
706            ("past", 1.5),
707            ("timeline", 2.0),
708        ],
709    },
710    // ── Gnosis / Transparency ──────────────────────────────────────
711    ToolProfile {
712        tool_name: "gnosis.status",
713        keywords: &[
714            ("gnosis", 3.0),
715            ("transparency", 3.0),
716            ("governance", 2.0),
717            ("status", 2.0),
718            ("overview", 2.0),
719            ("full", 1.5),
720            ("layers", 2.0),
721        ],
722    },
723    ToolProfile {
724        tool_name: "gnosis.history",
725        keywords: &[
726            ("gnosis", 2.5),
727            ("history", 3.0),
728            ("transparency", 2.0),
729            ("governance", 1.5),
730            ("audit", 2.0),
731            ("past", 1.5),
732        ],
733    },
734    ToolProfile {
735        tool_name: "gnosis.explain",
736        keywords: &[
737            ("gnosis", 2.0),
738            ("explain", 3.0),
739            ("why", 3.0),
740            ("blocked", 2.5),
741            ("allowed", 2.5),
742            ("verdict", 3.0),
743            ("reason", 2.5),
744            ("governance", 1.5),
745        ],
746    },
747    // ── Agents ─────────────────────────────────────────────────────
748    ToolProfile {
749        tool_name: "agent.register",
750        keywords: &[
751            ("register", 3.0),
752            ("agent", 3.0),
753            ("new", 2.0),
754            ("create", 2.0),
755            ("add", 2.0),
756            ("enroll", 2.5),
757        ],
758    },
759    ToolProfile {
760        tool_name: "agent.list",
761        keywords: &[
762            ("list", 2.5),
763            ("show", 2.0),
764            ("all", 1.5),
765            ("agents", 3.0),
766            ("roster", 2.5),
767        ],
768    },
769    ToolProfile {
770        tool_name: "agent.heartbeat",
771        keywords: &[
772            ("heartbeat", 3.0),
773            ("alive", 2.5),
774            ("ping", 2.5),
775            ("agent", 2.0),
776            ("status", 1.5),
777            ("check", 1.5),
778        ],
779    },
780    // ── Agent management (Tier 6) ────────────────────────────────
781    ToolProfile {
782        tool_name: "agent.trust",
783        keywords: &[
784            ("trust", 4.0),
785            ("reliability", 3.0),
786            ("confidence", 2.5),
787            ("agent", 2.0),
788            ("score", 2.0),
789            ("rating", 2.0),
790        ],
791    },
792    ToolProfile {
793        tool_name: "agent.descriptions",
794        keywords: &[
795            ("description", 4.0),
796            ("describe", 3.5),
797            ("agent", 2.0),
798            ("info", 2.0),
799            ("about", 2.0),
800            ("profile", 2.5),
801        ],
802    },
803    ToolProfile {
804        tool_name: "agent.capabilities",
805        keywords: &[
806            ("capabilities", 4.0),
807            ("capability", 3.5),
808            ("skills", 3.0),
809            ("agent", 2.0),
810            ("abilities", 2.5),
811            ("features", 2.0),
812        ],
813    },
814    ToolProfile {
815        tool_name: "agent.heartbeat.history",
816        keywords: &[
817            ("heartbeat", 3.5),
818            ("history", 3.5),
819            ("agent", 2.0),
820            ("log", 2.5),
821            ("record", 2.0),
822            ("past", 2.0),
823        ],
824    },
825    ToolProfile {
826        tool_name: "agent.deregister",
827        keywords: &[
828            ("deregister", 4.0),
829            ("unregister", 4.0),
830            ("remove", 3.0),
831            ("delete", 2.5),
832            ("agent", 2.0),
833            ("revoke", 3.0),
834        ],
835    },
836    // ── Tasks ──────────────────────────────────────────────────────
837    ToolProfile {
838        tool_name: "task.distribute",
839        keywords: &[
840            ("distribute", 3.0),
841            ("assign", 2.5),
842            ("dispatch", 2.0),
843            ("task", 3.0),
844            ("delegate", 2.5),
845            ("allocate", 2.0),
846        ],
847    },
848    ToolProfile {
849        tool_name: "task.status",
850        keywords: &[
851            ("task", 2.5),
852            ("status", 3.0),
853            ("progress", 2.5),
854            ("check", 2.0),
855            ("track", 2.0),
856        ],
857    },
858    // ── System ─────────────────────────────────────────────────────
859    ToolProfile {
860        tool_name: "system.health",
861        keywords: &[
862            ("system", 2.5),
863            ("health", 3.0),
864            ("check", 2.0),
865            ("diagnostic", 3.0),
866            ("doctor", 2.5),
867            ("status", 1.5),
868        ],
869    },
870    ToolProfile {
871        tool_name: "system.config",
872        keywords: &[
873            ("system", 2.0),
874            ("config", 3.0),
875            ("configuration", 3.0),
876            ("settings", 2.5),
877            ("info", 2.0),
878            ("setup", 2.0),
879        ],
880    },
881    ToolProfile {
882        tool_name: "system.flush",
883        keywords: &[
884            ("flush", 3.0),
885            ("garbage", 2.5),
886            ("collect", 2.0),
887            ("gc", 3.0),
888            ("cleanup", 2.5),
889            ("purge", 1.5),
890            ("clear", 1.5),
891        ],
892    },
893    // ── Knowledge graph ───────────────────────────────────────────
894    ToolProfile {
895        tool_name: "kg.extract",
896        keywords: &[
897            ("extract", 3.0),
898            ("entity", 3.0),
899            ("entities", 2.5),
900            ("relationship", 2.5),
901            ("triple", 2.5),
902            ("knowledge", 2.0),
903            ("graph", 2.0),
904            ("ner", 3.0),
905        ],
906    },
907    ToolProfile {
908        tool_name: "kg.query",
909        keywords: &[
910            ("knowledge", 2.0),
911            ("graph", 2.0),
912            ("relationship", 2.5),
913            ("entity", 2.0),
914            ("connected", 2.0),
915            ("subgraph", 3.0),
916            ("neighborhood", 2.0),
917        ],
918    },
919    ToolProfile {
920        tool_name: "kg.top",
921        keywords: &[
922            ("hub", 3.0),
923            ("god", 2.5),
924            ("node", 2.0),
925            ("top", 2.5),
926            ("ranked", 2.0),
927            ("central", 2.5),
928            ("important", 1.5),
929            ("knowledge", 1.5),
930            ("graph", 1.5),
931        ],
932    },
933    // ── Graph traversal ───────────────────────────────────────────
934    ToolProfile {
935        tool_name: "graph.walk",
936        keywords: &[
937            ("walk", 3.0),
938            ("traverse", 3.0),
939            ("bfs", 3.0),
940            ("explore", 2.0),
941            ("path", 2.0),
942            ("hop", 2.5),
943            ("follow", 2.0),
944            ("graph", 1.5),
945        ],
946    },
947    ToolProfile {
948        tool_name: "graph.community",
949        keywords: &[
950            ("community", 3.0),
951            ("cluster", 3.0),
952            ("communities", 2.5),
953            ("label", 2.0),
954            ("propagation", 2.5),
955            ("group", 2.0),
956            ("modularity", 2.5),
957        ],
958    },
959    ToolProfile {
960        tool_name: "graph.propagate",
961        keywords: &[
962            ("propagate", 3.0),
963            ("activation", 3.0),
964            ("spread", 2.5),
965            ("ripple", 2.5),
966            ("diffuse", 2.0),
967            ("energy", 2.0),
968            ("signal", 2.0),
969        ],
970    },
971    // ── Galaxy management ─────────────────────────────────────────
972    ToolProfile {
973        tool_name: "galaxy.transfer",
974        keywords: &[
975            ("transfer", 4.0),
976            ("move", 3.0),
977            ("relocate", 3.0),
978            ("migrate", 2.5),
979            ("galaxy", 1.5),
980        ],
981    },
982    ToolProfile {
983        tool_name: "galaxy.merge",
984        keywords: &[
985            ("merge", 4.0),
986            ("combine", 2.5),
987            ("unify", 2.5),
988            ("galaxy", 1.5),
989            ("absorb", 2.0),
990        ],
991    },
992    ToolProfile {
993        tool_name: "galaxy.snapshot",
994        keywords: &[
995            ("snapshot", 4.0),
996            ("backup", 3.0),
997            ("checkpoint", 2.5),
998            ("capture", 2.0),
999            ("galaxy", 1.5),
1000            ("preserve", 2.0),
1001        ],
1002    },
1003    ToolProfile {
1004        tool_name: "galaxy.restore",
1005        keywords: &[
1006            ("restore", 4.0),
1007            ("recover", 3.0),
1008            ("rollback", 3.0),
1009            ("revert", 2.5),
1010            ("galaxy", 1.5),
1011            ("undo", 2.0),
1012        ],
1013    },
1014    // ── Galaxy management (Tier 6) ───────────────────────────────
1015    ToolProfile {
1016        tool_name: "galaxy.dashboard",
1017        keywords: &[
1018            ("dashboard", 4.0),
1019            ("overview", 3.5),
1020            ("summary", 3.0),
1021            ("galaxy", 2.0),
1022            ("panel", 2.5),
1023            ("report", 2.0),
1024        ],
1025    },
1026    ToolProfile {
1027        tool_name: "galaxy.backup",
1028        keywords: &[
1029            ("backup", 4.0),
1030            ("archive", 3.0),
1031            ("dump", 3.0),
1032            ("galaxy", 2.0),
1033            ("save", 2.0),
1034            ("copy", 2.0),
1035        ],
1036    },
1037    ToolProfile {
1038        tool_name: "galaxy.taxonomy",
1039        keywords: &[
1040            ("taxonomy", 4.0),
1041            ("classification", 3.0),
1042            ("categories", 2.5),
1043            ("galaxy", 2.0),
1044            ("list", 1.5),
1045            ("types", 2.0),
1046        ],
1047    },
1048    ToolProfile {
1049        tool_name: "galaxy.purge",
1050        keywords: &[
1051            ("purge", 4.0),
1052            ("clear", 3.0),
1053            ("wipe", 3.5),
1054            ("empty", 2.5),
1055            ("galaxy", 2.0),
1056            ("clean", 2.5),
1057        ],
1058    },
1059    ToolProfile {
1060        tool_name: "galaxy.health",
1061        keywords: &[
1062            ("health", 4.0),
1063            ("diagnostic", 3.0),
1064            ("checkup", 3.0),
1065            ("galaxy", 2.0),
1066            ("status", 2.0),
1067            ("integrity", 2.5),
1068        ],
1069    },
1070    // ── Archaeology & learning ────────────────────────────────────
1071    ToolProfile {
1072        tool_name: "archaeology.search",
1073        keywords: &[
1074            ("archaeology", 4.0),
1075            ("excavate", 3.5),
1076            ("strata", 3.0),
1077            ("layer", 2.5),
1078            ("depth", 2.0),
1079            ("history", 2.0),
1080            ("timeline", 2.5),
1081            ("evolution", 2.0),
1082            ("oldest", 2.0),
1083            ("newest", 2.0),
1084        ],
1085    },
1086    ToolProfile {
1087        tool_name: "learning.pattern",
1088        keywords: &[
1089            ("learning", 3.0),
1090            ("pattern", 3.0),
1091            ("recurring", 2.5),
1092            ("theme", 2.5),
1093            ("frequency", 2.0),
1094            ("co-occurrence", 3.0),
1095            ("trends", 2.5),
1096            ("repeated", 2.0),
1097            ("common", 1.5),
1098        ],
1099    },
1100    ToolProfile {
1101        tool_name: "learning.suggest",
1102        keywords: &[
1103            ("suggest", 3.5),
1104            ("suggestion", 3.5),
1105            ("learn", 2.5),
1106            ("gap", 3.0),
1107            ("missing", 2.0),
1108            ("explore", 2.0),
1109            ("next", 2.0),
1110            ("recommend", 2.5),
1111            ("path", 2.0),
1112            ("advice", 2.0),
1113        ],
1114    },
1115    // ── Reasoning ─────────────────────────────────────────────────
1116    ToolProfile {
1117        tool_name: "bicameral.reason",
1118        keywords: &[
1119            ("bicameral", 5.0),
1120            ("hemisphere", 4.0),
1121            ("debate", 4.0),
1122            ("consensus", 3.5),
1123            ("deliberate", 3.5),
1124            ("pros", 3.0),
1125            ("cons", 3.0),
1126            ("dual", 2.5),
1127            ("perspective", 2.5),
1128            ("callosum", 4.0),
1129        ],
1130    },
1131    ToolProfile {
1132        tool_name: "bicameral.status",
1133        keywords: &[
1134            ("bicameral", 3.0),
1135            ("hemisphere", 3.5),
1136            ("callosum", 3.0),
1137            ("left", 1.5),
1138            ("right", 1.5),
1139            ("status", 2.0),
1140        ],
1141    },
1142    ToolProfile {
1143        tool_name: "reasoning.bicameral",
1144        keywords: &[
1145            ("bicameral", 3.0),
1146            ("pros", 3.5),
1147            ("cons", 3.5),
1148            ("debate", 2.5),
1149            ("perspective", 2.5),
1150            ("argument", 2.5),
1151            ("supporting", 2.0),
1152            ("opposing", 2.5),
1153            ("evidence", 2.0),
1154            ("analyze", 1.5),
1155        ],
1156    },
1157    // ── Drive & Emotion (R7) ───────────────────────────────────────
1158    ToolProfile {
1159        tool_name: "drive.snapshot",
1160        keywords: &[
1161            ("drive", 4.0),
1162            ("emotion", 4.0),
1163            ("motivation", 3.5),
1164            ("curiosity", 3.0),
1165            ("satisfaction", 3.0),
1166            ("caution", 2.5),
1167            ("energy", 2.0),
1168            ("mood", 3.0),
1169            ("feeling", 2.5),
1170        ],
1171    },
1172    ToolProfile {
1173        tool_name: "drive.event",
1174        keywords: &[
1175            ("drive", 3.0),
1176            ("emotion", 3.0),
1177            ("inject", 3.5),
1178            ("trigger", 2.5),
1179            ("reward", 3.0),
1180            ("frustration", 3.0),
1181            ("novelty", 2.5),
1182        ],
1183    },
1184    ToolProfile {
1185        tool_name: "think",
1186        keywords: &[
1187            ("think", 4.0),
1188            ("analyze", 3.0),
1189            ("reason", 2.5),
1190            ("consider", 2.0),
1191            ("reflect", 2.5),
1192            ("ponder", 3.0),
1193            ("contemplate", 3.0),
1194            ("insight", 2.0),
1195            ("thought", 3.0),
1196        ],
1197    },
1198    ToolProfile {
1199        tool_name: "explain",
1200        keywords: &[
1201            ("explain", 4.0),
1202            ("explanation", 3.5),
1203            ("clarify", 3.0),
1204            ("describe", 2.5),
1205            ("context", 2.0),
1206            ("related", 2.0),
1207            ("understand", 2.0),
1208            ("elaborate", 2.5),
1209            ("meaning", 2.0),
1210        ],
1211    },
1212    // ── Pipeline & skills ─────────────────────────────────────────
1213    ToolProfile {
1214        tool_name: "pipeline.create",
1215        keywords: &[
1216            ("pipeline", 4.0),
1217            ("create", 2.5),
1218            ("build", 2.0),
1219            ("workflow", 3.0),
1220            ("steps", 2.0),
1221            ("chain", 2.0),
1222            ("sequence", 2.0),
1223        ],
1224    },
1225    ToolProfile {
1226        tool_name: "pipeline.list",
1227        keywords: &[
1228            ("pipeline", 3.5),
1229            ("list", 3.0),
1230            ("workflows", 2.5),
1231            ("show", 1.5),
1232            ("available", 2.0),
1233        ],
1234    },
1235    ToolProfile {
1236        tool_name: "pipeline.status",
1237        keywords: &[
1238            ("pipeline", 3.0),
1239            ("status", 3.0),
1240            ("check", 2.0),
1241            ("state", 2.0),
1242            ("progress", 2.5),
1243            ("running", 2.0),
1244        ],
1245    },
1246    ToolProfile {
1247        tool_name: "skill.invoke",
1248        keywords: &[
1249            ("skill", 4.0),
1250            ("invoke", 3.5),
1251            ("execute", 2.5),
1252            ("run", 2.0),
1253            ("call", 2.0),
1254            ("trigger", 2.0),
1255            ("ability", 2.5),
1256        ],
1257    },
1258    ToolProfile {
1259        tool_name: "skill.list",
1260        keywords: &[
1261            ("skill", 3.5),
1262            ("list", 3.0),
1263            ("abilities", 2.5),
1264            ("available", 2.0),
1265            ("show", 1.5),
1266            ("capabilities", 2.0),
1267        ],
1268    },
1269    // ── Anomaly & state ───────────────────────────────────────────
1270    ToolProfile {
1271        tool_name: "anomaly.detect",
1272        keywords: &[
1273            ("anomaly", 4.0),
1274            ("detect", 3.0),
1275            ("outlier", 3.5),
1276            ("unusual", 2.5),
1277            ("abnormal", 3.0),
1278            ("strange", 2.0),
1279            ("irregular", 2.5),
1280            ("z-score", 3.0),
1281        ],
1282    },
1283    ToolProfile {
1284        tool_name: "state.snapshot",
1285        keywords: &[
1286            ("snapshot", 4.0),
1287            ("capture", 2.5),
1288            ("state", 2.5),
1289            ("checkpoint", 3.0),
1290            ("preserve", 2.0),
1291            ("record", 2.0),
1292        ],
1293    },
1294    ToolProfile {
1295        tool_name: "state.revert",
1296        keywords: &[
1297            ("revert", 4.0),
1298            ("rollback", 3.5),
1299            ("restore", 3.0),
1300            ("previous", 2.5),
1301            ("undo", 2.5),
1302            ("go back", 2.0),
1303            ("state", 2.0),
1304        ],
1305    },
1306    // ── Correlation & god nodes ──────────────────────────────────
1307    ToolProfile {
1308        tool_name: "correlation.analyze",
1309        keywords: &[
1310            ("correlation", 4.0),
1311            ("analyze", 2.0),
1312            ("co-occurrence", 3.0),
1313            ("phi", 2.5),
1314            ("relationship", 2.0),
1315            ("statistical", 2.5),
1316            ("connect", 1.5),
1317            ("associate", 1.5),
1318        ],
1319    },
1320    ToolProfile {
1321        tool_name: "god.nodes",
1322        keywords: &[
1323            ("god", 3.5),
1324            ("nodes", 3.0),
1325            ("hub", 3.0),
1326            ("central", 2.5),
1327            ("important", 2.0),
1328            ("connector", 3.0),
1329            ("cross-galaxy", 3.0),
1330            ("entity", 2.0),
1331        ],
1332    },
1333    // ── Anti-loop & boundary ──────────────────────────────────────
1334    ToolProfile {
1335        tool_name: "anti_loop.check",
1336        keywords: &[
1337            ("loop", 4.0),
1338            ("anti", 2.5),
1339            ("repetitive", 3.0),
1340            ("duplicate", 3.0),
1341            ("stuck", 3.0),
1342            ("cycle", 2.5),
1343            ("repeated", 2.5),
1344            ("burst", 2.0),
1345        ],
1346    },
1347    ToolProfile {
1348        tool_name: "boundary.enforce",
1349        keywords: &[
1350            ("boundary", 4.0),
1351            ("enforce", 3.5),
1352            ("limit", 3.0),
1353            ("violation", 3.0),
1354            ("overflow", 3.0),
1355            ("constraint", 2.5),
1356            ("check", 2.0),
1357            ("resource", 2.0),
1358            ("sprawl", 2.5),
1359        ],
1360    },
1361    // ── Tier 5: Net tools ──────────────────────────────────────────
1362    ToolProfile {
1363        tool_name: "association.mine",
1364        keywords: &[
1365            ("cross", 5.0),
1366            ("galaxy", 4.0),
1367            ("association", 3.0),
1368            ("mine", 3.0),
1369            ("overlap", 2.5),
1370            ("keyword", 2.0),
1371            ("propose", 2.0),
1372            ("link", 2.0),
1373        ],
1374    },
1375    ToolProfile {
1376        tool_name: "pattern.detect",
1377        keywords: &[
1378            ("pattern", 4.0),
1379            ("detect", 3.5),
1380            ("structural", 3.0),
1381            ("hub", 3.0),
1382            ("bridge", 3.0),
1383            ("chain", 2.5),
1384            ("graph", 2.0),
1385            ("topology", 2.5),
1386        ],
1387    },
1388    ToolProfile {
1389        tool_name: "emergence.report",
1390        keywords: &[
1391            ("emergence", 4.0),
1392            ("report", 3.0),
1393            ("tag", 2.5),
1394            ("frequency", 2.5),
1395            ("distribution", 2.0),
1396            ("trend", 2.0),
1397            ("emerging", 3.0),
1398            ("dominant", 2.0),
1399        ],
1400    },
1401    ToolProfile {
1402        tool_name: "network.stats",
1403        keywords: &[
1404            ("network", 4.0),
1405            ("stats", 3.0),
1406            ("density", 3.0),
1407            ("degree", 2.5),
1408            ("edge", 2.0),
1409            ("node", 2.0),
1410            ("global", 2.0),
1411            ("graph", 2.0),
1412        ],
1413    },
1414    ToolProfile {
1415        tool_name: "network.centrality",
1416        keywords: &[
1417            ("centrality", 4.0),
1418            ("central", 3.0),
1419            ("degree", 3.0),
1420            ("important", 2.0),
1421            ("influential", 2.5),
1422            ("hub", 2.0),
1423            ("rank", 2.0),
1424            ("top", 2.0),
1425        ],
1426    },
1427    ToolProfile {
1428        tool_name: "network.clusters",
1429        keywords: &[
1430            ("cluster", 4.0),
1431            ("clusters", 3.0),
1432            ("component", 3.0),
1433            ("connected", 2.5),
1434            ("group", 2.0),
1435            ("isolate", 2.0),
1436            ("subgraph", 2.5),
1437        ],
1438    },
1439    // ── Tier 5: Ghost tools ────────────────────────────────────────
1440    ToolProfile {
1441        tool_name: "smarana.status",
1442        keywords: &[
1443            ("smarana", 5.0),
1444            ("retention", 4.0),
1445            ("recall", 3.0),
1446            ("score", 2.0),
1447            ("memory", 1.5),
1448            ("forgetting", 2.5),
1449        ],
1450    },
1451    ToolProfile {
1452        tool_name: "smarana.trace",
1453        keywords: &[
1454            ("smarana", 4.0),
1455            ("trace", 3.5),
1456            ("decay", 3.0),
1457            ("retention", 3.0),
1458            ("over", 2.0),
1459            ("time", 1.5),
1460            ("history", 2.0),
1461        ],
1462    },
1463    ToolProfile {
1464        tool_name: "apotheosis.check",
1465        keywords: &[
1466            ("apotheosis", 5.0),
1467            ("self", 2.5),
1468            ("improvement", 3.5),
1469            ("trend", 3.0),
1470            ("progress", 2.5),
1471            ("check", 2.0),
1472            ("score", 2.0),
1473        ],
1474    },
1475    ToolProfile {
1476        tool_name: "citta.history",
1477        keywords: &[
1478            ("citta", 4.0),
1479            ("history", 3.5),
1480            ("heartbeat", 3.0),
1481            ("valence", 2.5),
1482            ("past", 2.0),
1483            ("recent", 2.0),
1484            ("consciousness", 2.5),
1485        ],
1486    },
1487    ToolProfile {
1488        tool_name: "dream.analyze",
1489        keywords: &[
1490            ("dream", 4.0),
1491            ("analyze", 3.5),
1492            ("analysis", 3.0),
1493            ("consolidation", 2.5),
1494            ("quality", 2.5),
1495            ("sleep", 2.0),
1496            ("cycle", 2.0),
1497        ],
1498    },
1499    ToolProfile {
1500        tool_name: "consciousness.depth",
1501        keywords: &[
1502            ("consciousness", 4.0),
1503            ("depth", 4.0),
1504            ("deep", 3.0),
1505            ("measure", 2.5),
1506            ("awareness", 2.5),
1507            ("state", 2.0),
1508            ("level", 2.0),
1509        ],
1510    },
1511    // ── Tier 7: WinnowingBasket tools ──────────────────────────────
1512    ToolProfile {
1513        tool_name: "memory.sort",
1514        keywords: &[
1515            ("sort", 4.0),
1516            ("order", 3.5),
1517            ("arrange", 3.0),
1518            ("rank", 2.5),
1519            ("by", 1.5),
1520            ("importance", 2.0),
1521            ("recency", 2.0),
1522            ("memory", 1.5),
1523        ],
1524    },
1525    ToolProfile {
1526        tool_name: "memory.filter",
1527        keywords: &[
1528            ("filter", 4.0),
1529            ("where", 2.5),
1530            ("match", 2.0),
1531            ("criteria", 3.0),
1532            ("condition", 2.5),
1533            ("tag", 2.0),
1534            ("importance", 1.5),
1535            ("memory", 1.5),
1536        ],
1537    },
1538    ToolProfile {
1539        tool_name: "memory.deduplicate",
1540        keywords: &[
1541            ("deduplicate", 4.0),
1542            ("dedup", 4.0),
1543            ("duplicate", 3.5),
1544            ("unique", 2.5),
1545            ("distinct", 3.0),
1546            ("remove", 2.0),
1547            ("redundant", 3.0),
1548            ("memory", 1.5),
1549        ],
1550    },
1551    ToolProfile {
1552        tool_name: "memory.export",
1553        keywords: &[
1554            ("export", 4.0),
1555            ("download", 3.0),
1556            ("dump", 3.0),
1557            ("extract", 2.5),
1558            ("format", 2.0),
1559            ("csv", 3.5),
1560            ("markdown", 3.0),
1561            ("memory", 1.5),
1562        ],
1563    },
1564    // ── Tier 7: Dipper tools ───────────────────────────────────────
1565    ToolProfile {
1566        tool_name: "homeostasis.check",
1567        keywords: &[
1568            ("homeostasis", 5.0),
1569            ("check", 3.0),
1570            ("balance", 3.5),
1571            ("equilibrium", 3.0),
1572            ("health", 2.5),
1573            ("metrics", 3.0),
1574            ("vitals", 3.0),
1575            ("system", 1.5),
1576        ],
1577    },
1578    ToolProfile {
1579        tool_name: "homeostasis.adjust",
1580        keywords: &[
1581            ("homeostasis", 4.0),
1582            ("adjust", 3.5),
1583            ("tune", 3.0),
1584            ("rebalance", 3.5),
1585            ("weight", 3.0),
1586            ("simulate", 2.5),
1587            ("recalibrate", 3.0),
1588        ],
1589    },
1590    ToolProfile {
1591        tool_name: "homeostasis.history",
1592        keywords: &[
1593            ("homeostasis", 4.0),
1594            ("history", 3.5),
1595            ("past", 2.5),
1596            ("trend", 3.0),
1597            ("samples", 3.0),
1598            ("readings", 2.5),
1599            ("timeline", 2.0),
1600        ],
1601    },
1602    ToolProfile {
1603        tool_name: "homeostasis.alerts",
1604        keywords: &[
1605            ("homeostasis", 4.0),
1606            ("alert", 4.0),
1607            ("alerts", 4.0),
1608            ("warning", 3.5),
1609            ("critical", 3.0),
1610            ("notify", 2.5),
1611            ("threshold", 2.5),
1612            ("triggered", 2.0),
1613        ],
1614    },
1615    // ── v4: Reflex tools ──────────────────────────────────────────
1616    ToolProfile {
1617        tool_name: "reflex.dispatch",
1618        keywords: &[
1619            ("reflex", 5.0),
1620            ("dispatch", 5.0),
1621            ("trigger", 4.0),
1622            ("invoke", 3.0),
1623            ("fire", 3.0),
1624            ("handler", 2.5),
1625            ("emergency", 2.0),
1626            ("e_stop", 3.5),
1627            ("estop", 3.5),
1628            ("safety", 2.0),
1629            ("actuator", 2.0),
1630        ],
1631    },
1632    ToolProfile {
1633        tool_name: "reflex.status",
1634        keywords: &[
1635            ("reflex", 5.0),
1636            ("status", 4.0),
1637            ("table", 3.0),
1638            ("handler", 2.5),
1639            ("registered", 2.5),
1640            ("safety_mask", 3.0),
1641            ("dispatch_count", 3.0),
1642            ("builtins", 2.0),
1643        ],
1644    },
1645    // ── v4: Workspace tools ───────────────────────────────────────
1646    ToolProfile {
1647        tool_name: "workspace.spotlight",
1648        keywords: &[
1649            ("spotlight", 5.0),
1650            ("attention", 4.0),
1651            ("focus", 3.0),
1652            ("arbitration", 3.5),
1653            ("workspace", 3.0),
1654            ("current", 2.0),
1655            ("holder", 2.5),
1656            ("salience", 2.5),
1657            ("who", 1.5),
1658            ("winning", 2.5),
1659        ],
1660    },
1661    ToolProfile {
1662        tool_name: "workspace.events",
1663        keywords: &[
1664            ("workspace", 4.0),
1665            ("events", 5.0),
1666            ("backlog", 4.0),
1667            ("history", 2.5),
1668            ("log", 2.0),
1669        ],
1670    },
1671    ToolProfile {
1672        tool_name: "workspace.publish",
1673        keywords: &[
1674            ("workspace", 4.0),
1675            ("publish", 5.0),
1676            ("broadcast", 4.0),
1677            ("emit", 3.5),
1678            ("send", 2.5),
1679            ("event", 3.0),
1680            ("post", 2.5),
1681            ("notify", 2.0),
1682            ("submit", 2.5),
1683        ],
1684    },
1685    ToolProfile {
1686        tool_name: "workspace.stats",
1687        keywords: &[
1688            ("workspace", 4.0),
1689            ("stats", 5.0),
1690            ("statistics", 4.5),
1691            ("transfers", 3.0),
1692            ("arbitration", 2.5),
1693            ("published", 3.0),
1694            ("count", 2.0),
1695            ("summary", 2.5),
1696        ],
1697    },
1698    // ── v4: Timescale tools ───────────────────────────────────────
1699    ToolProfile {
1700        tool_name: "timescale.status",
1701        keywords: &[
1702            ("timescale", 5.0),
1703            ("status", 4.0),
1704            ("tier", 3.0),
1705            ("tiers", 3.0),
1706            ("bus", 2.5),
1707            ("brain_wave", 3.0),
1708            ("active", 2.5),
1709            ("hooks", 2.0),
1710            ("interval", 2.0),
1711            ("budget", 2.0),
1712        ],
1713    },
1714    ToolProfile {
1715        tool_name: "timescale.hooks",
1716        keywords: &[
1717            ("timescale", 4.0),
1718            ("hooks", 5.0),
1719            ("hook", 4.0),
1720            ("list", 2.5),
1721            ("stats", 2.5),
1722            ("performance", 2.5),
1723            ("tick", 3.0),
1724            ("timeout", 2.5),
1725            ("duration", 2.0),
1726            ("callback", 2.0),
1727        ],
1728    },
1729    // ── Self-model (R4) ─────────────────────────────────────────────
1730    ToolProfile {
1731        tool_name: "selfmodel.forecast",
1732        keywords: &[
1733            ("forecast", 5.0),
1734            ("predict", 4.0),
1735            ("prediction", 4.0),
1736            ("project", 3.0),
1737            ("projection", 3.0),
1738            ("extrapolate", 4.0),
1739            ("trend", 3.0),
1740            ("outlook", 3.0),
1741            ("selfmodel", 5.0),
1742            ("introspect", 3.0),
1743            ("horizon", 2.5),
1744            ("metric", 2.0),
1745        ],
1746    },
1747    ToolProfile {
1748        tool_name: "selfmodel.alerts",
1749        keywords: &[
1750            ("alert", 5.0),
1751            ("alerts", 5.0),
1752            ("warning", 3.5),
1753            ("warnings", 3.5),
1754            ("critical", 3.5),
1755            ("threshold", 3.0),
1756            ("breach", 3.0),
1757            ("exceed", 2.5),
1758            ("danger", 3.0),
1759            ("selfmodel", 5.0),
1760            ("introspect", 3.0),
1761        ],
1762    },
1763    ToolProfile {
1764        tool_name: "selfmodel.snapshot",
1765        keywords: &[
1766            ("snapshot", 5.0),
1767            ("selfmodel", 5.0),
1768            ("introspect", 4.0),
1769            ("introspection", 4.0),
1770            ("overview", 3.0),
1771            ("confidence", 3.0),
1772            ("conservative", 2.5),
1773        ],
1774    },
1775    // ── RSI: Friction & Improvement ───────────────────────────────
1776    ToolProfile {
1777        tool_name: "friction.log",
1778        keywords: &[
1779            ("friction", 5.0),
1780            ("log", 3.0),
1781            ("report", 2.5),
1782            ("issue", 3.5),
1783            ("problem", 3.0),
1784            ("bug", 3.0),
1785            ("complaint", 3.0),
1786            ("annoying", 2.5),
1787            ("broken", 2.5),
1788            ("wrong", 2.0),
1789        ],
1790    },
1791    ToolProfile {
1792        tool_name: "friction.review",
1793        keywords: &[
1794            ("friction", 4.5),
1795            ("review", 4.0),
1796            ("issues", 3.5),
1797            ("problems", 3.0),
1798            ("patterns", 2.5),
1799            ("summary", 2.5),
1800            ("analyze", 2.0),
1801            ("frictions", 4.0),
1802        ],
1803    },
1804    ToolProfile {
1805        tool_name: "improve.proposals",
1806        keywords: &[
1807            ("improve", 5.0),
1808            ("improvement", 5.0),
1809            ("improvements", 5.0),
1810            ("proposal", 4.0),
1811            ("proposals", 4.0),
1812            ("suggest", 3.0),
1813            ("suggestions", 3.0),
1814            ("fix", 2.5),
1815            ("friction", 2.0),
1816            ("upgrade", 3.0),
1817            ("enhance", 2.5),
1818            ("better", 2.0),
1819        ],
1820    },
1821    ToolProfile {
1822        tool_name: "redteam.proposals",
1823        keywords: &[
1824            ("redteam", 5.0),
1825            ("red", 2.0),
1826            ("team", 2.0),
1827            ("adversarial", 5.0),
1828            ("attack", 4.5),
1829            ("vulnerability", 4.5),
1830            ("security", 4.0),
1831            ("exploit", 4.0),
1832            ("breach", 3.5),
1833            ("pentest", 4.5),
1834            ("penetrate", 3.5),
1835            ("break", 3.0),
1836            ("threat", 3.5),
1837            ("probe", 3.0),
1838            ("audit", 2.5),
1839        ],
1840    },
1841    // ── Sensorimotor / Embodiment I/O ───────────────────────────────
1842    ToolProfile {
1843        tool_name: "sensor.list",
1844        keywords: &[
1845            ("sensor", 5.0),
1846            ("sensors", 5.0),
1847            ("list", 3.0),
1848            ("hardware", 3.0),
1849            ("devices", 2.5),
1850            ("thermal", 2.0),
1851            ("battery", 2.0),
1852        ],
1853    },
1854    ToolProfile {
1855        tool_name: "sensor.read",
1856        keywords: &[
1857            ("read", 4.0),
1858            ("sensor", 4.0),
1859            ("temperature", 3.5),
1860            ("value", 3.0),
1861            ("measure", 3.0),
1862            ("probe", 2.5),
1863        ],
1864    },
1865    ToolProfile {
1866        tool_name: "sensor.poll",
1867        keywords: &[
1868            ("poll", 5.0),
1869            ("sample", 4.0),
1870            ("all", 2.5),
1871            ("sensors", 3.0),
1872            ("readings", 3.5),
1873            ("collect", 2.5),
1874        ],
1875    },
1876    ToolProfile {
1877        tool_name: "sensor.history",
1878        keywords: &[
1879            ("history", 5.0),
1880            ("past", 3.0),
1881            ("readings", 3.5),
1882            ("recent", 3.0),
1883            ("log", 2.5),
1884            ("timeseries", 3.5),
1885        ],
1886    },
1887    ToolProfile {
1888        tool_name: "actuator.list",
1889        keywords: &[
1890            ("actuator", 5.0),
1891            ("actuators", 5.0),
1892            ("motor", 3.0),
1893            ("relay", 3.0),
1894            ("output", 2.5),
1895        ],
1896    },
1897    ToolProfile {
1898        tool_name: "actuator.command",
1899        keywords: &[
1900            ("command", 4.5),
1901            ("send", 3.5),
1902            ("actuator", 4.0),
1903            ("motor", 3.0),
1904            ("drive", 3.0),
1905            ("control", 3.5),
1906            ("set", 2.0),
1907        ],
1908    },
1909    ToolProfile {
1910        tool_name: "actuator.estop",
1911        keywords: &[
1912            ("estop", 5.0),
1913            ("emergency", 5.0),
1914            ("stop", 4.0),
1915            ("halt", 4.0),
1916            ("abort", 3.5),
1917            ("shutdown", 3.0),
1918        ],
1919    },
1920    ToolProfile {
1921        tool_name: "reflex.list",
1922        keywords: &[
1923            ("reflex", 5.0),
1924            ("reflexes", 5.0),
1925            ("rules", 3.0),
1926            ("trigger", 2.5),
1927        ],
1928    },
1929    ToolProfile {
1930        tool_name: "reflex.add",
1931        keywords: &[
1932            ("add", 3.5),
1933            ("reflex", 4.5),
1934            ("rule", 4.0),
1935            ("create", 3.0),
1936            ("threshold", 3.5),
1937            ("trigger", 3.0),
1938        ],
1939    },
1940    ToolProfile {
1941        tool_name: "reflex.evaluate",
1942        keywords: &[
1943            ("evaluate", 5.0),
1944            ("check", 3.0),
1945            ("reflex", 4.0),
1946            ("trigger", 3.5),
1947            ("fire", 3.0),
1948            ("respond", 2.5),
1949        ],
1950    },
1951    ToolProfile {
1952        tool_name: "sensorimotor.scan",
1953        keywords: &[
1954            ("sensorimotor", 6.0),
1955            ("scan", 4.0),
1956            ("poll", 3.5),
1957            ("reflex", 3.0),
1958            ("autonomous", 3.0),
1959            ("embodiment", 4.0),
1960            ("cycle", 2.5),
1961            ("self-regulate", 3.0),
1962        ],
1963    },
1964    // ── Gnosis fallback ────────────────────────────────────────────
1965    ToolProfile {
1966        tool_name: "gnosis",
1967        keywords: &[
1968            ("help", 2.0),
1969            ("discover", 2.0),
1970            ("what", 1.5),
1971            ("can", 1.0),
1972            ("do", 1.0),
1973            ("status", 1.5),
1974            ("overview", 2.0),
1975            ("system", 1.0),
1976        ],
1977    },
1978    // ── Speculative decoding ──────────────────────────────────────
1979    ToolProfile {
1980        tool_name: "speculative.decode",
1981        keywords: &[
1982            ("speculative", 5.0),
1983            ("decode", 4.0),
1984            ("draft", 3.0),
1985            ("verify", 2.5),
1986            ("accelerate", 3.0),
1987            ("speedup", 3.0),
1988            ("fast", 2.0),
1989            ("infer", 2.0),
1990            ("generate", 1.5),
1991        ],
1992    },
1993    ToolProfile {
1994        tool_name: "speculative.stats",
1995        keywords: &[
1996            ("speculative", 4.0),
1997            ("acceptance", 3.0),
1998            ("speedup", 3.0),
1999            ("draft", 2.0),
2000            ("latency", 2.0),
2001            ("tokens", 2.0),
2002        ],
2003    },
2004    // ── Meta-harness ───────────────────────────────────────────────
2005    ToolProfile {
2006        tool_name: "meta.enhance",
2007        keywords: &[
2008            ("enhance", 5.0),
2009            ("grounding", 4.0),
2010            ("grounded", 4.0),
2011            ("rag", 4.0),
2012            ("self-correct", 4.0),
2013            ("selfcorrect", 4.0),
2014            ("ensemble", 3.5),
2015            ("improve", 3.0),
2016            ("cognitive", 3.0),
2017            ("meta", 2.5),
2018            ("harness", 3.0),
2019            ("augment", 2.5),
2020            ("refine", 2.0),
2021        ],
2022    },
2023    ToolProfile {
2024        tool_name: "meta.stats",
2025        keywords: &[
2026            ("meta", 3.0),
2027            ("harness", 3.0),
2028            ("enhancement", 3.0),
2029            ("improvement", 3.0),
2030            ("enhance", 2.0),
2031            ("stats", 2.0),
2032        ],
2033    },
2034    // ── Dense encoding ──────────────────────────────────────────────
2035    ToolProfile {
2036        tool_name: "dense.encode",
2037        keywords: &[
2038            ("dense", 5.0),
2039            ("compress", 4.0),
2040            ("compression", 4.0),
2041            ("encode", 3.5),
2042            ("encoding", 3.5),
2043            ("token", 2.5),
2044            ("compact", 3.0),
2045            ("shrink", 2.5),
2046            ("cjk", 3.0),
2047        ],
2048    },
2049    ToolProfile {
2050        tool_name: "dense.decode",
2051        keywords: &[
2052            ("decode", 4.0),
2053            ("decompress", 4.0),
2054            ("expand", 3.0),
2055            ("restore", 2.5),
2056            ("dense", 2.0),
2057        ],
2058    },
2059    // ── Transaction tools ──────────────────────────────────────────
2060    ToolProfile {
2061        tool_name: "transaction.begin",
2062        keywords: &[
2063            ("transaction", 5.0),
2064            ("begin", 4.0),
2065            ("start", 3.0),
2066            ("snapshot", 3.5),
2067            ("checkpoint", 3.0),
2068            ("atomic", 3.0),
2069        ],
2070    },
2071    ToolProfile {
2072        tool_name: "transaction.commit",
2073        keywords: &[
2074            ("transaction", 5.0),
2075            ("commit", 5.0),
2076            ("finalize", 3.5),
2077            ("confirm", 3.0),
2078            ("keep", 2.5),
2079            ("persist", 3.0),
2080        ],
2081    },
2082    ToolProfile {
2083        tool_name: "transaction.rollback",
2084        keywords: &[
2085            ("transaction", 5.0),
2086            ("rollback", 5.0),
2087            ("revert", 4.0),
2088            ("undo", 4.0),
2089            ("restore", 3.5),
2090            ("abort", 3.5),
2091            ("discard", 3.0),
2092        ],
2093    },
2094    // ── Imagination Engine ─────────────────────────────────────────
2095    ToolProfile {
2096        tool_name: "imagine.scenario",
2097        keywords: &[
2098            ("imagine", 5.0),
2099            ("scenario", 5.0),
2100            ("scenarios", 4.5),
2101            ("plan", 3.0),
2102            ("contingency", 4.0),
2103            ("what-if", 4.0),
2104            ("possibility", 3.5),
2105            ("options", 2.5),
2106            ("alternatives", 3.0),
2107            ("brainstorm", 3.5),
2108            ("envision", 3.5),
2109        ],
2110    },
2111    ToolProfile {
2112        tool_name: "imagine.predict",
2113        keywords: &[
2114            ("predict", 5.0),
2115            ("outcome", 4.0),
2116            ("consequence", 4.0),
2117            ("forecast", 3.0),
2118            ("expect", 3.0),
2119            ("result", 2.5),
2120            ("happen", 3.5),
2121            ("would", 2.5),
2122            ("if", 1.5),
2123            ("imagine", 2.0),
2124        ],
2125    },
2126    ToolProfile {
2127        tool_name: "imagine.reflect",
2128        keywords: &[
2129            ("reflect", 5.0),
2130            ("counterfactual", 5.0),
2131            ("regret", 4.0),
2132            ("alternative", 3.5),
2133            ("should", 3.0),
2134            ("instead", 3.0),
2135            ("what-if", 3.0),
2136            ("reconsider", 4.0),
2137            ("retrospect", 3.5),
2138            ("lesson", 3.0),
2139            ("counter", 2.5),
2140            ("factual", 2.5),
2141        ],
2142    },
2143    // ── NLU observability ───────────────────────────────────────────
2144    ToolProfile {
2145        tool_name: "nlu.shadow_report",
2146        keywords: &[
2147            ("shadow", 4.0),
2148            ("disagreement", 4.0),
2149            ("nlu", 3.0),
2150            ("router", 3.0),
2151            ("embedding", 2.5),
2152            ("tfidf", 2.5),
2153            ("tf-idf", 2.5),
2154            ("oats", 3.0),
2155            ("promotion", 2.5),
2156            ("routing", 2.0),
2157        ],
2158    },
2159];
2160
2161/// Common English stopwords that don't contribute to tool routing.
2162/// These are filtered out during tokenization to improve cosine similarity.
2163const STOPWORDS: &[&str] = &[
2164    // Articles
2165    "a", "an", "the", // Demonstratives
2166    "this", "that", "these", "those", // Pronouns
2167    "i", "me", "my", "you", "your", "yours", "it", "its", "we", "our", "ours", "they", "them",
2168    "their", "theirs", "he", "him", "his", "she", "her", "hers", // Auxiliary verbs
2169    "is", "are", "was", "were", "be", "been", "being", "am", "have", "has", "had", "will", "would",
2170    "could", "should", "shall", "must", // Prepositions
2171    "in", "on", "at", "to", "for", "of", "with", "by", "from", "into", "about", "over", "under",
2172    "through", "between", "among", "during", "before", "after", "above", "below",
2173    // Conjunctions
2174    "and", "but", "or", "nor", "so", "yet", // Negation/affirmation
2175    "not", "no", "yes", // Conditionals
2176    "if", "else", "because", "as", "until", "while", "although", "though", "since", "unless",
2177    "whether", // Direction/position
2178    "up", "down", "out", "off", "again", "further", "then", "once", "here", "there", "when",
2179    "where", "why", "how", // Quantifiers (non-routing)
2180    "all", "any", "both", "each", "few", "more", "most", "other", "some", "such", "only", "own",
2181    "same", "than", "too", "very", // Time/manner
2182    "just", "also", "now",
2183];
2184
2185/// Bases of common English verbs that drop a trailing 'e' before -ing/-ed suffixes.
2186/// When stemming removes -ing or -ed and the base is in this set, 'e' is restored.
2187/// Sorted for binary search.
2188const E_DROPPING_BASES: &[&str] = &[
2189    "activat",
2190    "allocat",
2191    "arrang",
2192    "associat",
2193    "becom",
2194    "calculat",
2195    "chang",
2196    "clos",
2197    "cit",
2198    "configur",
2199    "consolidat",
2200    "continu",
2201    "creat",
2202    "delegat",
2203    "delet",
2204    "demonstrat",
2205    "downgrad",
2206    "encourag",
2207    "engag",
2208    "ensur",
2209    "enumerat",
2210    "evaluat",
2211    "exchang",
2212    "exclud",
2213    "explor",
2214    "fac",
2215    "generat",
2216    "giv",
2217    "improv",
2218    "includ",
2219    "leav",
2220    "lik",
2221    "mak",
2222    "manag",
2223    "measur",
2224    "merg",
2225    "mov",
2226    "navigat",
2227    "notic",
2228    "operat",
2229    "practic",
2230    "relat",
2231    "remov",
2232    "restor",
2233    "sav",
2234    "simulat",
2235    "stor",
2236    "tak",
2237    "updat",
2238    "upgrad",
2239    "us",
2240    "validat",
2241    "writ",
2242];
2243
2244/// Simple English stemmer for common suffixes.
2245/// Reduces words to their root form to improve matching.
2246/// Examples: "memories" → "memory", "searching" → "search", "stored" → "store"
2247fn stem(word: &str) -> String {
2248    let w = word.to_lowercase();
2249
2250    // Handle -ies → -y (categories → category, memories → memory)
2251    if w.ends_with("ies") && w.len() > 3 {
2252        let base = &w[..w.len() - 3];
2253        return format!("{base}y");
2254    }
2255
2256    // Handle -ing (searching → search, storing → store)
2257    if w.ends_with("ing") && w.len() > 4 {
2258        let base = &w[..w.len() - 3];
2259        // Double consonant check: running → run (character-aware — the old
2260        // byte-index comparison could panic on non-ASCII words like "xéing"
2261        // and "éing", which slice mid-character).
2262        if let Some(last) = base.chars().last() {
2263            let is_double_consonant = base.chars().rev().nth(1) == Some(last);
2264            if is_double_consonant {
2265                return base[..base.len() - last.len_utf8()].to_string();
2266            }
2267        }
2268        // Check if this base needs 'e' restoration (e-dropping verb)
2269        if E_DROPPING_BASES.binary_search(&base).is_ok() {
2270            return format!("{base}e");
2271        }
2272        return base.to_string();
2273    }
2274
2275    // Handle -ed (stored → store, searched → search)
2276    if w.ends_with("ed") && w.len() > 3 {
2277        let base = &w[..w.len() - 2];
2278        // Check if this base needs 'e' restoration (e-dropping verb)
2279        if E_DROPPING_BASES.binary_search(&base).is_ok() {
2280            return format!("{base}e");
2281        }
2282        return base.to_string();
2283    }
2284
2285    // Handle -es (searches → search, batches → batch)
2286    if w.ends_with("es") && w.len() > 3 {
2287        let base = &w[..w.len() - 2];
2288        // ch/sh/s/x/z endings: searches → search
2289        if base.ends_with("ch")
2290            || base.ends_with("sh")
2291            || base.ends_with('s')
2292            || base.ends_with('x')
2293            || base.ends_with('z')
2294        {
2295            return base.to_string();
2296        }
2297        return w[..w.len() - 1].to_string();
2298    }
2299
2300    // Handle -s (simple plural: tags → tag, lists → list)
2301    if w.ends_with('s') && w.len() > 2 && !w.ends_with("ss") {
2302        return w[..w.len() - 1].to_string();
2303    }
2304
2305    w
2306}
2307
2308/// Tokenize text into lowercase terms, filtering out stopwords and applying stemming.
2309/// Splits on non-alphanumeric characters (simple but effective for routing).
2310fn tokenize(text: &str) -> Vec<String> {
2311    text.split(|c: char| !c.is_alphanumeric())
2312        .filter(|s| !s.is_empty())
2313        .map(str::to_lowercase)
2314        .filter(|s| !STOPWORDS.contains(&s.as_str()))
2315        .map(|s| stem(&s))
2316        .collect()
2317}
2318
2319/// Build a term-frequency map from tokens.
2320fn term_frequencies(tokens: &[String]) -> AHashMap<String, f64> {
2321    let mut tf = AHashMap::new();
2322    for token in tokens {
2323        *tf.entry(token.clone()).or_insert(0.0) += 1.0;
2324    }
2325    tf
2326}
2327
2328/// Compute cosine similarity between an input TF vector and a tool profile.
2329///
2330/// The profile's keywords form a weighted vector. The input is a TF vector.
2331/// Both input tokens and profile keywords are stemmed before comparison.
2332/// Cosine similarity = dot(input, profile) / (|input| * |profile|).
2333fn cosine_similarity(input_tf: &AHashMap<String, f64>, profile: &ToolProfile) -> f64 {
2334    let mut dot_product = 0.0;
2335    let mut profile_norm_sq = 0.0;
2336
2337    for (term, weight) in profile.keywords {
2338        profile_norm_sq += weight * weight;
2339        let stemmed_term = stem(term);
2340        if let Some(&freq) = input_tf.get(&stemmed_term) {
2341            dot_product += freq * weight;
2342        }
2343    }
2344
2345    if profile_norm_sq == 0.0 {
2346        return 0.0;
2347    }
2348
2349    let input_norm: f64 = input_tf.values().map(|v| v * v).sum::<f64>().sqrt();
2350    if input_norm == 0.0 {
2351        return 0.0;
2352    }
2353
2354    dot_product / (input_norm * profile_norm_sq.sqrt())
2355}
2356
2357/// Classify natural language input into (tool_name, confidence) using
2358/// TF-IDF cosine similarity against all tool profiles.
2359///
2360/// Returns the best-matching tool name and its similarity score (0.0–1.0).
2361/// Command verbs that strongly indicate a specific tool when they appear
2362/// as the first word of the input. This helps counteract cosine similarity's
2363/// bias toward profiles with fewer keywords (smaller norm).
2364pub const PREFIX_ROUTES: &[(&str, &str, f64)] = &[
2365    ("remember", "memory.create", 1.5),
2366    ("store", "memory.create", 1.5),
2367    ("save", "memory.create", 1.5),
2368    ("memorize", "memory.create", 1.5),
2369    ("resume", "session.continuity", 1.5),
2370    ("recall", "memory.search", 1.5),
2371    ("search", "memory.search", 1.3),
2372    ("find", "memory.search", 1.4),
2373    ("list", "memory.list", 1.3),
2374    ("delete", "memory.delete", 1.5),
2375    ("remove", "memory.delete", 1.3),
2376    ("forget", "memory.delete", 1.5),
2377    ("count", "memory.count", 1.5),
2378    ("show", "gnosis", 1.0),
2379    ("spotlight", "workspace.spotlight", 1.5),
2380    ("publish", "workspace.publish", 1.5),
2381    ("broadcast", "workspace.publish", 1.4),
2382    ("forecast", "selfmodel.forecast", 1.5),
2383    ("deliberate", "bicameral.reason", 1.3),
2384    ("drive", "drive.snapshot", 1.5),
2385    ("emotion", "drive.snapshot", 1.4),
2386    ("adversarial", "redteam.proposals", 1.5),
2387    ("redteam", "redteam.proposals", 1.5),
2388    ("pentest", "redteam.proposals", 1.5),
2389    ("friction", "friction.log", 1.3),
2390    ("log", "friction.log", 1.4),
2391    ("sensor", "sensor.list", 1.5),
2392    ("actuator", "actuator.list", 1.5),
2393    ("estop", "actuator.estop", 1.5),
2394    ("emergency", "actuator.estop", 1.3),
2395    ("sensorimotor", "sensorimotor.scan", 1.5),
2396    ("imagine", "imagine.scenario", 1.5),
2397    ("envision", "imagine.scenario", 1.4),
2398    ("brainstorm", "imagine.scenario", 1.3),
2399    ("counterfactual", "imagine.reflect", 1.5),
2400];
2401
2402/// Multi-word intentions checked before the single-word table.
2403///
2404/// Natural phrasing ("what do you remember about X", "what did we decide
2405/// about X") lands on `memory.search` instead of falling through to gnosis.
2406/// The contract stays explicit `route=` dispatch; these are the
2407/// high-frequency conveniences the grimoire teaches first.
2408pub const PHRASE_ROUTES: &[(&str, &str, f64)] = &[
2409    ("what do you remember about", "memory.search", 1.5),
2410    ("what did we decide about", "memory.search", 1.5),
2411    ("do you remember", "memory.search", 1.4),
2412    ("look up", "memory.search", 1.4),
2413    // Continuity phrases (9.1.6): resume intentions land on
2414    // session.continuity, not gnosis or session.recall. The continuity
2415    // profile alone loses to smaller-norm profiles on cosine; these
2416    // decisive phrases are the grimoire-taught conveniences.
2417    ("where were we", "session.continuity", 1.5),
2418    ("where did we leave off", "session.continuity", 1.5),
2419    ("what did we decide last", "session.continuity", 1.5),
2420    ("continue from", "session.continuity", 1.4),
2421    ("pick up where", "session.continuity", 1.4),
2422];
2423
2424/// If no profile scores above the minimum threshold, falls back to "gnosis"
2425/// with confidence 0.0.
2426#[must_use]
2427pub fn classify(text: &str) -> (&'static str, f64) {
2428    let (tool, confidence, _) = classify_with_alternative(text);
2429    (tool, confidence)
2430}
2431
2432/// Like [`classify`], but also returns the runner-up candidate when one
2433/// exists (score > 0 and a different tool).
2434///
2435/// Callers disclose it as a `suggested_route` when the top guess is weak, so
2436/// a low-confidence dispatch can be confirmed explicitly instead of silently
2437/// trusted.
2438#[must_use]
2439pub fn classify_with_alternative(text: &str) -> (&'static str, f64, Option<(&'static str, f64)>) {
2440    let lower = text.to_lowercase();
2441    if lower.trim().is_empty() {
2442        return ("gnosis", 0.0, None);
2443    }
2444
2445    // Curated multi-word intentions are decisive: they are explicit enough
2446    // that profile scoring (which needs lexical overlap) should not veto
2447    // them — "what do you remember about X" shares no keywords with
2448    // memory.search yet is exactly a search.
2449    //
2450    // This check must run BEFORE tokenization: an all-stopword phrase
2451    // ("where were we") reduces to zero content tokens, and the
2452    // empty-token fallback below used to fire first — silently abstaining
2453    // on a phrase the product advertises (9.1.6 audit finding).
2454    let probe = lower.trim_start();
2455    if let Some((_, tool, _)) = PHRASE_ROUTES
2456        .iter()
2457        .find(|(phrase, _, _)| probe.starts_with(phrase))
2458    {
2459        return (tool, 1.0, None);
2460    }
2461
2462    let tokens = tokenize(&lower);
2463    if tokens.is_empty() {
2464        return ("gnosis", 0.0, None);
2465    }
2466
2467    let input_tf = term_frequencies(&tokens);
2468
2469    // Check for prefix-based routing bonus (single-word command verbs).
2470    let first_word = probe.split_whitespace().next().unwrap_or("");
2471    let prefix_bonus: Option<(&str, f64)> = PREFIX_ROUTES
2472        .iter()
2473        .find(|(verb, _, _)| *verb == first_word)
2474        .map(|(_, tool, bonus)| (*tool, *bonus));
2475
2476    let mut best_tool = "gnosis";
2477    let mut best_score = 0.0;
2478    let mut second: Option<(&'static str, f64)> = None;
2479
2480    for profile in TOOL_PROFILES {
2481        let mut score = cosine_similarity(&input_tf, profile);
2482        // Apply prefix routing: bonus to matching tool, penalty to non-matching
2483        if let Some((bonus_tool, bonus)) = prefix_bonus {
2484            if profile.tool_name == bonus_tool {
2485                score *= bonus;
2486            } else {
2487                // Dampen non-matching tools to respect prefix intent
2488                score /= bonus;
2489            }
2490        }
2491        if score > best_score {
2492            if best_score > 0.0 {
2493                second = Some((best_tool, best_score));
2494            }
2495            best_score = score;
2496            best_tool = profile.tool_name;
2497        } else if score > 0.0
2498            && profile.tool_name != best_tool
2499            && second.is_none_or(|(_, s)| score > s)
2500        {
2501            second = Some((profile.tool_name, score));
2502        }
2503    }
2504
2505    // Minimum confidence threshold — below this, fall back to gnosis. The
2506    // weak top candidate is still disclosed as an alternative so callers
2507    // see "did you mean X?" instead of a bare failure.
2508    const MIN_THRESHOLD: f64 = 0.10;
2509    if best_score < MIN_THRESHOLD {
2510        let alternative = (best_score > 0.0).then_some((best_tool, best_score));
2511        return ("gnosis", 0.0, alternative);
2512    }
2513
2514    (
2515        best_tool,
2516        best_score,
2517        second.filter(|(tool, _)| *tool != best_tool),
2518    )
2519}
2520
2521#[cfg(test)]
2522fn profiled_tools() -> Vec<&'static str> {
2523    TOOL_PROFILES.iter().map(|p| p.tool_name).collect()
2524}
2525
2526#[cfg(test)]
2527fn profile_count() -> usize {
2528    TOOL_PROFILES.len()
2529}
2530
2531#[cfg(test)]
2532mod tests {
2533    use super::*;
2534    use std::collections::HashSet;
2535
2536    #[test]
2537    fn classify_empty_returns_gnosis() {
2538        let (tool, conf) = classify("");
2539        assert_eq!(tool, "gnosis");
2540        assert_eq!(conf, 0.0);
2541    }
2542
2543    #[test]
2544    fn classify_alternative_discloses_a_runner_up() {
2545        // Contract: when candidates compete, the runner-up is disclosed
2546        // (score > 0, different tool) rather than silently dropped; the
2547        // alternative never outranks the top candidate.
2548        let (tool, confidence, alternative) =
2549            classify_with_alternative("recall beta quartz submarine design notes");
2550        assert!(confidence > 0.0, "expected a scored candidate: {tool}");
2551        if let Some((alt_tool, alt_confidence)) = alternative {
2552            assert_ne!(alt_tool, tool, "alternative must be a different tool");
2553            assert!(
2554                alt_confidence <= confidence,
2555                "alternative {alt_tool} ({alt_confidence}) beat {tool} ({confidence})"
2556            );
2557        }
2558    }
2559
2560    #[test]
2561    fn classify_unknown_offers_no_alternative() {
2562        let (tool, confidence, alternative) = classify_with_alternative("zzzqqx vvbnm");
2563        assert_eq!(tool, "gnosis");
2564        assert_eq!(confidence, 0.0);
2565        assert!(
2566            alternative.is_none(),
2567            "no candidate should surface for pure noise: {alternative:?}"
2568        );
2569    }
2570
2571    #[test]
2572    fn classify_whitespace_returns_gnosis() {
2573        let (tool, conf) = classify("   ");
2574        assert_eq!(tool, "gnosis");
2575        assert_eq!(conf, 0.0);
2576    }
2577
2578    #[test]
2579    fn classify_unknown_returns_gnosis() {
2580        let (tool, conf) = classify("xyzzy frobnicate");
2581        assert_eq!(tool, "gnosis");
2582        assert_eq!(conf, 0.0);
2583    }
2584
2585    #[test]
2586    fn classify_remember_routes_to_memory_create() {
2587        let (tool, _conf) = classify("remember that the sky is blue");
2588        assert_eq!(tool, "memory.create");
2589    }
2590
2591    #[test]
2592    fn classify_store_routes_to_memory_create() {
2593        let (tool, _conf) = classify("store this important fact");
2594        assert_eq!(tool, "memory.create");
2595    }
2596
2597    #[test]
2598    fn classify_recall_routes_to_memory_search() {
2599        // Human phrasing: "recall X" is a lookup, not an id read
2600        // (first-run feedback, 2026-09-14).
2601        let (tool, _conf) = classify("recall the last memory");
2602        assert_eq!(tool, "memory.search");
2603    }
2604
2605    #[test]
2606    fn classify_find_routes_to_memory_search() {
2607        let (tool, _conf) = classify("find BETA quartz submarine in memory");
2608        assert_eq!(tool, "memory.search");
2609    }
2610
2611    #[test]
2612    fn classify_what_do_you_remember_routes_to_memory_search() {
2613        let (tool, _conf) = classify("What do you remember about BETA quartz submarine?");
2614        assert_eq!(tool, "memory.search");
2615    }
2616
2617    #[test]
2618    fn classify_what_did_we_decide_routes_to_memory_search() {
2619        let (tool, _conf) = classify("What did we decide about BETA quartz submarine?");
2620        assert_eq!(tool, "memory.search");
2621    }
2622
2623    #[test]
2624    fn classify_look_up_routes_to_memory_search() {
2625        let (tool, _conf) = classify("look up the quartz submarine");
2626        assert_eq!(tool, "memory.search");
2627    }
2628
2629    #[test]
2630    fn classify_search_routes_to_memory_search() {
2631        let (tool, _conf) = classify("search for rust");
2632        assert_eq!(tool, "memory.search");
2633    }
2634
2635    #[test]
2636    fn classify_list_memories_routes_to_memory_list() {
2637        let (tool, _conf) = classify("list memories in codex");
2638        assert_eq!(tool, "memory.list");
2639    }
2640
2641    #[test]
2642    fn classify_delete_memory_routes_to_memory_delete() {
2643        let (tool, _conf) = classify("delete memory abc-123");
2644        assert_eq!(tool, "memory.delete");
2645    }
2646
2647    #[test]
2648    fn classify_karma_routes_to_karma_report() {
2649        let (tool, _conf) = classify("show me the karma report");
2650        assert_eq!(tool, "karma.report");
2651    }
2652
2653    #[test]
2654    fn classify_karma_history_routes_correctly() {
2655        let (tool, _conf) = classify("karma history");
2656        assert_eq!(tool, "karma.history");
2657    }
2658
2659    #[test]
2660    fn classify_dharma_status_routes_correctly() {
2661        let (tool, _conf) = classify("dharma status");
2662        assert_eq!(tool, "dharma.status");
2663    }
2664
2665    #[test]
2666    fn classify_dharma_rules_routes_correctly() {
2667        let (tool, _conf) = classify("show dharma rules");
2668        assert_eq!(tool, "dharma.rules");
2669    }
2670
2671    #[test]
2672    fn classify_harmony_routes_to_harmony_vector() {
2673        let (tool, _conf) = classify("harmony vector status");
2674        assert_eq!(tool, "harmony.vector");
2675    }
2676
2677    #[test]
2678    fn classify_gnosis_explain_routes_correctly() {
2679        let (tool, _conf) = classify("why was my action blocked");
2680        assert_eq!(tool, "gnosis.explain");
2681    }
2682
2683    #[test]
2684    fn classify_session_start_routes_correctly() {
2685        let (tool, _conf) = classify("start session research");
2686        assert_eq!(tool, "session.start");
2687    }
2688
2689    #[test]
2690    fn classify_session_end_routes_correctly() {
2691        let (tool, _conf) = classify("end session abc-123");
2692        assert_eq!(tool, "session.end");
2693    }
2694
2695    #[test]
2696    fn classify_session_continuity_routes_correctly() {
2697        // 9.1.6: continuity phrases must reach session.continuity, not gnosis.
2698        for phrase in [
2699            "what did we decide last time",
2700            "resume where we left off",
2701            "where were we in the previous session",
2702            "continue from where I stopped",
2703            "pick up where we left off",
2704        ] {
2705            let (tool, conf) = classify(phrase);
2706            assert_eq!(tool, "session.continuity", "phrase: {phrase}");
2707            assert!(conf > 0.1, "phrase {phrase} confidence too low: {conf}");
2708        }
2709    }
2710
2711    #[test]
2712    fn classify_bare_where_were_we_routes_to_continuity() {
2713        // 9.1.6 audit regression: the bare phrase is all stopwords, so the
2714        // tokenized classifier used to abstain before the phrase table ran.
2715        let (tool, conf) = classify("where were we");
2716        assert_eq!(tool, "session.continuity");
2717        assert_eq!(conf, 1.0);
2718        let (tool, conf) = classify("Where were we?");
2719        assert_eq!(tool, "session.continuity");
2720        assert_eq!(conf, 1.0);
2721    }
2722
2723    #[test]
2724    fn classify_every_advertised_phrase_bare_and_with_suffix() {
2725        // Every phrase in the routing table is part of the product contract;
2726        // each must classify as its declared tool both bare (the exact
2727        // phrase a user types) and with trailing content.
2728        for (phrase, tool, _) in PHRASE_ROUTES {
2729            let (bare_tool, bare_conf) = classify(phrase);
2730            assert_eq!(&bare_tool, tool, "bare phrase {phrase:?}");
2731            assert_eq!(bare_conf, 1.0, "bare phrase {phrase:?} confidence");
2732            let suffixed = format!("{phrase} tomorrow please");
2733            let (suffixed_tool, suffixed_conf) = classify(&suffixed);
2734            assert_eq!(&suffixed_tool, tool, "suffixed phrase {suffixed:?}");
2735            assert_eq!(
2736                suffixed_conf, 1.0,
2737                "suffixed phrase {suffixed:?} confidence"
2738            );
2739        }
2740    }
2741
2742    #[test]
2743    fn classify_session_record_routes_correctly() {
2744        let (tool, _conf) = classify("record this decision for later");
2745        assert_eq!(tool, "session.record");
2746    }
2747
2748    #[test]
2749    fn classify_citta_status_routes_correctly() {
2750        let (tool, _conf) = classify("citta status");
2751        assert_eq!(tool, "citta.status");
2752    }
2753
2754    #[test]
2755    fn classify_dream_trigger_routes_correctly() {
2756        let (tool, _conf) = classify("trigger dream cycle");
2757        assert_eq!(tool, "dream.trigger");
2758    }
2759
2760    #[test]
2761    fn classify_consolidate_routes_correctly() {
2762        let (tool, _conf) = classify("consolidate duplicate memories");
2763        assert_eq!(tool, "memory.consolidate");
2764    }
2765
2766    #[test]
2767    fn classify_emergence_scan_routes_correctly() {
2768        let (tool, _conf) = classify("emergence scan for trending tags");
2769        assert_eq!(tool, "emergence.scan");
2770    }
2771
2772    #[test]
2773    fn classify_spiral_report_routes_correctly() {
2774        let (tool, _conf) = classify("spiral report for autonomy");
2775        assert_eq!(tool, "spiral.report");
2776    }
2777
2778    #[test]
2779    fn classify_retention_prune_routes_correctly() {
2780        let (tool, _conf) = classify("prune memories ready to forget");
2781        assert_eq!(tool, "retention.prune");
2782    }
2783
2784    #[test]
2785    fn classify_tools_list_routes_correctly() {
2786        let (tool, _conf) = classify("list tools");
2787        assert_eq!(tool, "tools.list");
2788    }
2789
2790    #[test]
2791    fn classify_system_health_routes_correctly() {
2792        let (tool, _conf) = classify("system health check");
2793        assert_eq!(tool, "system.health");
2794    }
2795
2796    #[test]
2797    fn classify_agent_register_routes_correctly() {
2798        let (tool, _conf) = classify("register agent worker-1");
2799        assert_eq!(tool, "agent.register");
2800    }
2801
2802    #[test]
2803    fn classify_task_distribute_routes_correctly() {
2804        let (tool, _conf) = classify("distribute task analyze data");
2805        assert_eq!(tool, "task.distribute");
2806    }
2807
2808    #[test]
2809    fn classify_nearby_memories_routes_correctly() {
2810        // "find X" is a search intention taught by the grimoire; the
2811        // specialist nearby route stays reachable without the verb.
2812        let (tool, _conf) = classify("memories nearby");
2813        assert_eq!(tool, "memory.nearby");
2814    }
2815
2816    #[test]
2817    fn classify_hybrid_recall_routes_correctly() {
2818        let (tool, _conf) = classify("hybrid recall for rust");
2819        assert_eq!(tool, "memory.hybrid_recall");
2820    }
2821
2822    #[test]
2823    fn classify_galaxy_stats_routes_correctly() {
2824        let (tool, _conf) = classify("galaxy stats overview");
2825        assert_eq!(tool, "galaxy.stats");
2826    }
2827
2828    #[test]
2829    fn classify_galaxy_export_routes_correctly() {
2830        let (tool, _conf) = classify("export galaxy backup");
2831        assert_eq!(tool, "galaxy.export");
2832    }
2833
2834    #[test]
2835    fn classify_kg_extract_routes_correctly() {
2836        let (tool, _conf) = classify("extract entities knowledge graph");
2837        assert_eq!(tool, "kg.extract");
2838    }
2839
2840    #[test]
2841    fn classify_kg_query_routes_correctly() {
2842        let (tool, _conf) = classify("knowledge graph query relationships");
2843        assert_eq!(tool, "kg.query");
2844    }
2845
2846    #[test]
2847    fn classify_kg_top_routes_correctly() {
2848        let (tool, _conf) = classify("top hub nodes knowledge graph");
2849        assert_eq!(tool, "kg.top");
2850    }
2851
2852    #[test]
2853    fn classify_graph_walk_routes_correctly() {
2854        let (tool, _conf) = classify("traverse graph walk bfs");
2855        assert_eq!(tool, "graph.walk");
2856    }
2857
2858    #[test]
2859    fn classify_graph_community_routes_correctly() {
2860        let (tool, _conf) = classify("detect communities clusters in graph");
2861        assert_eq!(tool, "graph.community");
2862    }
2863
2864    #[test]
2865    fn classify_graph_propagate_routes_correctly() {
2866        let (tool, _conf) = classify("propagate activation spread ripple");
2867        assert_eq!(tool, "graph.propagate");
2868    }
2869
2870    #[test]
2871    fn classify_galaxy_transfer_routes_correctly() {
2872        let (tool, _conf) = classify("transfer move memories galaxy");
2873        assert_eq!(tool, "galaxy.transfer");
2874    }
2875
2876    #[test]
2877    fn classify_galaxy_merge_routes_correctly() {
2878        let (tool, _conf) = classify("merge combine galaxies");
2879        assert_eq!(tool, "galaxy.merge");
2880    }
2881
2882    #[test]
2883    fn classify_galaxy_snapshot_routes_correctly() {
2884        let (tool, _conf) = classify("snapshot backup galaxy");
2885        assert_eq!(tool, "galaxy.snapshot");
2886    }
2887
2888    #[test]
2889    fn classify_galaxy_restore_routes_correctly() {
2890        let (tool, _conf) = classify("restore recover galaxy snapshot");
2891        assert_eq!(tool, "galaxy.restore");
2892    }
2893
2894    #[test]
2895    fn classify_agent_trust_routes_correctly() {
2896        let (tool, _conf) = classify("trust reliability agent score");
2897        assert_eq!(tool, "agent.trust");
2898    }
2899
2900    #[test]
2901    fn classify_agent_descriptions_routes_correctly() {
2902        let (tool, _conf) = classify("describe agent profile info");
2903        assert_eq!(tool, "agent.descriptions");
2904    }
2905
2906    #[test]
2907    fn classify_agent_capabilities_routes_correctly() {
2908        let (tool, _conf) = classify("agent capabilities skills abilities");
2909        assert_eq!(tool, "agent.capabilities");
2910    }
2911
2912    #[test]
2913    fn classify_agent_heartbeat_history_routes_correctly() {
2914        let (tool, _conf) = classify("heartbeat history log agent");
2915        assert_eq!(tool, "agent.heartbeat.history");
2916    }
2917
2918    #[test]
2919    fn classify_agent_deregister_routes_correctly() {
2920        let (tool, _conf) = classify("deregister unregister remove agent");
2921        assert_eq!(tool, "agent.deregister");
2922    }
2923
2924    #[test]
2925    fn classify_galaxy_dashboard_routes_correctly() {
2926        let (tool, _conf) = classify("galaxy dashboard overview panel");
2927        assert_eq!(tool, "galaxy.dashboard");
2928    }
2929
2930    #[test]
2931    fn classify_galaxy_backup_routes_correctly() {
2932        let (tool, _conf) = classify("backup archive galaxy dump");
2933        assert_eq!(tool, "galaxy.backup");
2934    }
2935
2936    #[test]
2937    fn classify_galaxy_taxonomy_routes_correctly() {
2938        let (tool, _conf) = classify("galaxy taxonomy classification categories");
2939        assert_eq!(tool, "galaxy.taxonomy");
2940    }
2941
2942    #[test]
2943    fn classify_galaxy_purge_routes_correctly() {
2944        let (tool, _conf) = classify("purge wipe clear galaxy");
2945        assert_eq!(tool, "galaxy.purge");
2946    }
2947
2948    #[test]
2949    fn classify_galaxy_health_routes_correctly() {
2950        let (tool, _conf) = classify("galaxy health diagnostic checkup");
2951        assert_eq!(tool, "galaxy.health");
2952    }
2953
2954    #[test]
2955    fn classify_memory_sort_routes_correctly() {
2956        let (tool, _conf) = classify("sort memories by importance");
2957        assert_eq!(tool, "memory.sort");
2958    }
2959
2960    #[test]
2961    fn classify_memory_filter_routes_correctly() {
2962        let (tool, _conf) = classify("filter memories by tag criteria");
2963        assert_eq!(tool, "memory.filter");
2964    }
2965
2966    #[test]
2967    fn classify_memory_deduplicate_routes_correctly() {
2968        let (tool, _conf) = classify("deduplicate memories redundant duplicate");
2969        assert_eq!(tool, "memory.deduplicate");
2970    }
2971
2972    #[test]
2973    fn classify_memory_export_routes_correctly() {
2974        let (tool, _conf) = classify("export memories csv format download");
2975        assert_eq!(tool, "memory.export");
2976    }
2977
2978    #[test]
2979    fn classify_homeostasis_check_routes_correctly() {
2980        let (tool, _conf) = classify("homeostasis check balance vitals metrics");
2981        assert_eq!(tool, "homeostasis.check");
2982    }
2983
2984    #[test]
2985    fn classify_homeostasis_adjust_routes_correctly() {
2986        let (tool, _conf) = classify("homeostasis adjust rebalance weight tune");
2987        assert_eq!(tool, "homeostasis.adjust");
2988    }
2989
2990    #[test]
2991    fn classify_homeostasis_history_routes_correctly() {
2992        let (tool, _conf) = classify("homeostasis history trend past samples");
2993        assert_eq!(tool, "homeostasis.history");
2994    }
2995
2996    #[test]
2997    fn classify_homeostasis_alerts_routes_correctly() {
2998        let (tool, _conf) = classify("homeostasis alerts warning critical threshold");
2999        assert_eq!(tool, "homeostasis.alerts");
3000    }
3001
3002    #[test]
3003    fn classify_reflex_dispatch_routes_correctly() {
3004        let (tool, _conf) = classify("dispatch reflex e_stop emergency handler");
3005        assert_eq!(tool, "reflex.dispatch");
3006    }
3007
3008    #[test]
3009    fn classify_reflex_status_routes_correctly() {
3010        let (tool, _conf) = classify("reflex status table registered handlers");
3011        assert_eq!(tool, "reflex.status");
3012    }
3013
3014    #[test]
3015    fn classify_workspace_spotlight_routes_correctly() {
3016        let (tool, _conf) = classify("workspace spotlight attention arbitration");
3017        assert_eq!(tool, "workspace.spotlight");
3018    }
3019
3020    #[test]
3021    fn classify_workspace_events_routes_correctly() {
3022        let (tool, _conf) = classify("workspace recent events backlog history");
3023        assert_eq!(tool, "workspace.events");
3024    }
3025
3026    #[test]
3027    fn classify_workspace_publish_routes_correctly() {
3028        let (tool, _conf) = classify("publish broadcast workspace event emit");
3029        assert_eq!(tool, "workspace.publish");
3030    }
3031
3032    #[test]
3033    fn classify_workspace_stats_routes_correctly() {
3034        let (tool, _conf) = classify("workspace stats statistics transfers count");
3035        assert_eq!(tool, "workspace.stats");
3036    }
3037
3038    #[test]
3039    fn classify_timescale_status_routes_correctly() {
3040        let (tool, _conf) = classify("timescale status tier bus brain_wave active");
3041        assert_eq!(tool, "timescale.status");
3042    }
3043
3044    #[test]
3045    fn classify_timescale_hooks_routes_correctly() {
3046        let (tool, _conf) = classify("timescale hooks list tick timeout performance");
3047        assert_eq!(tool, "timescale.hooks");
3048    }
3049
3050    #[test]
3051    fn classify_confidence_is_reasonable() {
3052        let (_tool, conf) = classify("remember that rust is fast");
3053        assert!(
3054            conf > 0.15,
3055            "confidence should be > 0.15 for clear match, got {conf}"
3056        );
3057    }
3058
3059    #[test]
3060    fn classify_case_insensitive() {
3061        let (tool1, _) = classify("REMEMBER THAT");
3062        let (tool2, _) = classify("remember that");
3063        assert_eq!(tool1, tool2);
3064    }
3065
3066    #[test]
3067    fn classify_partial_match_works() {
3068        let (tool, conf) = classify("search rust");
3069        assert_eq!(tool, "memory.search");
3070        assert!(conf > 0.0);
3071    }
3072
3073    #[test]
3074    fn classify_multi_word_query() {
3075        let (tool, _conf) = classify("show me the effectiveness report for tools");
3076        assert_eq!(tool, "tools.effectiveness_report");
3077    }
3078
3079    #[test]
3080    fn profile_count_is_reasonable() {
3081        // Should have 50+ profiles
3082        assert!(
3083            profile_count() >= 60,
3084            "expected 60+ profiles, got {}",
3085            profile_count()
3086        );
3087    }
3088
3089    #[test]
3090    fn profiled_tools_are_unique() {
3091        let tools = profiled_tools();
3092        let set: HashSet<&str> = tools.iter().copied().collect();
3093        assert_eq!(tools.len(), set.len(), "duplicate tool names in profiles");
3094    }
3095
3096    #[test]
3097    fn classify_unique_patterns_count() {
3098        let inputs = [
3099            "remember",
3100            "recall",
3101            "list memories",
3102            "delete memory",
3103            "search",
3104            "query",
3105            "associate",
3106            "associations",
3107            "consolidate",
3108            "decay",
3109            "batch read",
3110            "update memory",
3111            "tag memory",
3112            "memory stats",
3113            "hybrid recall",
3114            "count memories",
3115            "list tags",
3116            "mine associations",
3117            "start session",
3118            "checkpoint",
3119            "recall session",
3120            "end session",
3121            "list sessions",
3122            "citta status",
3123            "reflect",
3124            "coherence",
3125            "dream status",
3126            "trigger dream",
3127            "effectiveness",
3128            "retire tool",
3129            "pattern search",
3130            "salience",
3131            "serendipity",
3132            "detect clusters",
3133            "list constellations",
3134            "galaxy stats",
3135            "export galaxy",
3136            "import galaxy",
3137            "karma",
3138            "karma history",
3139            "clear karma",
3140            "dharma rules",
3141            "dharma audit",
3142            "dharma profiles",
3143            "dharma",
3144            "register agent",
3145            "list agents",
3146            "heartbeat",
3147            "distribute task",
3148            "task status",
3149            "system health",
3150            "system config",
3151            "flush",
3152            "tools",
3153            "nearby memories",
3154            "vector search",
3155            "find similar",
3156            "extract entities knowledge graph",
3157            "knowledge graph query",
3158            "top hub nodes",
3159            "traverse graph walk",
3160            "detect communities",
3161            "propagate activation",
3162            "transfer galaxy",
3163            "merge galaxies",
3164            "snapshot galaxy",
3165            "restore galaxy",
3166            "sort memories",
3167            "filter memories",
3168            "deduplicate memories",
3169            "export memories csv",
3170            "homeostasis check",
3171            "homeostasis adjust",
3172            "homeostasis history",
3173            "homeostasis alerts",
3174            "dispatch reflex e_stop",
3175            "reflex status table",
3176            "workspace spotlight attention",
3177            "workspace recent events",
3178            "publish workspace event",
3179            "workspace stats summary",
3180            "timescale status tiers",
3181            "timescale hooks tick",
3182        ];
3183        let mut tools: HashSet<&str> = HashSet::new();
3184        for input in &inputs {
3185            let (tool, _) = classify(input);
3186            tools.insert(tool);
3187        }
3188        assert!(
3189            tools.len() >= 30,
3190            "Expected 30+ unique NLU targets, got {}",
3191            tools.len()
3192        );
3193    }
3194
3195    #[test]
3196    fn classify_vector_search_routes_correctly() {
3197        let (tool, conf) = classify("vector search similar memories");
3198        assert_eq!(tool, "memory.vector.search");
3199        assert!(conf > 0.0);
3200    }
3201
3202    #[test]
3203    fn classify_embedding_search_routes_correctly() {
3204        let (tool, conf) = classify("embedding similarity lookup");
3205        assert_eq!(tool, "memory.vector.search");
3206        assert!(conf > 0.0);
3207    }
3208
3209    #[test]
3210    fn classify_semantic_search_routes_correctly() {
3211        let (tool, conf) = classify("semantic similarity search");
3212        assert_eq!(tool, "memory.vector.search");
3213        assert!(conf > 0.0);
3214    }
3215
3216    #[test]
3217    fn classify_stemming_handles_morphological_variants() {
3218        // -ing form should route same as base
3219        let (tool1, _) = classify("searching for rust");
3220        let (tool2, _) = classify("search for rust");
3221        assert_eq!(tool1, tool2);
3222
3223        // -ed form
3224        let (tool3, _) = classify("stored important fact");
3225        let (tool4, _) = classify("store important fact");
3226        assert_eq!(tool3, tool4);
3227
3228        // plural → singular
3229        let (tool5, _) = classify("list memories");
3230        let (tool6, _) = classify("list memory");
3231        assert_eq!(tool5, tool6);
3232    }
3233
3234    #[test]
3235    fn stem_handles_unicode_without_panicking() {
3236        // Regression: the -ing double-consonant check sliced by byte index,
3237        // panicking on words whose base ends in a multi-byte character.
3238        assert_eq!(stem("xéing"), "xé");
3239        assert_eq!(stem("éing"), "é");
3240        assert_eq!(stem("caféing"), "café");
3241        // ASCII behavior unchanged
3242        assert_eq!(stem("running"), "run");
3243        assert_eq!(stem("swimming"), "swim");
3244        assert_eq!(stem("searching"), "search");
3245        assert_eq!(stem("typing"), "typ");
3246        assert_eq!(stem("memories"), "memory");
3247        assert_eq!(stem("stored"), "store");
3248    }
3249
3250    #[test]
3251    fn classify_handles_unicode_thoughts_without_panicking() {
3252        // Full pipeline: tokenize → stem on multibyte input must not panic.
3253        let (tool, _conf) = classify("caféing sur les mémoires");
3254        assert!(!tool.is_empty());
3255        let (tool2, _conf2) = classify("mémoire éing recherche");
3256        assert!(!tool2.is_empty());
3257    }
3258
3259    #[test]
3260    fn classify_confidence_improved_with_stopwords() {
3261        // With stopwords filtered, confidence should be higher
3262        let (_, conf) = classify("remember that rust is fast");
3263        assert!(
3264            conf > 0.20,
3265            "confidence should be > 0.20 with stopword filtering, got {conf}"
3266        );
3267    }
3268
3269    // ── Self-model (R4) NLU routing tests ───────────────────────────
3270
3271    #[test]
3272    fn classify_forecast_routes_to_selfmodel_forecast() {
3273        let (tool, _conf) = classify("forecast cpu load for next 5 samples");
3274        assert_eq!(tool, "selfmodel.forecast");
3275    }
3276
3277    #[test]
3278    fn classify_predict_routes_to_selfmodel_forecast() {
3279        let (tool, _conf) = classify("predict memory pressure trend");
3280        assert_eq!(tool, "selfmodel.forecast");
3281    }
3282
3283    #[test]
3284    fn classify_alerts_routes_to_selfmodel_alerts() {
3285        let (tool, _conf) = classify("selfmodel alerts");
3286        assert_eq!(tool, "selfmodel.alerts");
3287    }
3288
3289    #[test]
3290    fn classify_warning_routes_to_selfmodel_alerts() {
3291        let (tool, _conf) = classify("selfmodel critical warnings");
3292        assert_eq!(tool, "selfmodel.alerts");
3293    }
3294
3295    #[test]
3296    fn classify_snapshot_routes_to_selfmodel_snapshot() {
3297        let (tool, _conf) = classify("selfmodel snapshot");
3298        assert_eq!(tool, "selfmodel.snapshot");
3299    }
3300
3301    #[test]
3302    fn classify_introspection_routes_to_selfmodel_snapshot() {
3303        let (tool, _conf) = classify("show introspection state overview");
3304        assert_eq!(tool, "selfmodel.snapshot");
3305    }
3306
3307    // ── Bicameral (R5) NLU routing tests ────────────────────────────
3308
3309    #[test]
3310    fn classify_bicameral_debate_routes_to_bicameral_reason() {
3311        let (tool, _conf) = classify("bicameral debate on rust vs python");
3312        assert_eq!(tool, "bicameral.reason");
3313    }
3314
3315    #[test]
3316    fn classify_hemisphere_consensus_routes_to_bicameral_reason() {
3317        let (tool, _conf) = classify("dual hemisphere consensus deliberation");
3318        assert_eq!(tool, "bicameral.reason");
3319    }
3320
3321    #[test]
3322    fn classify_bicameral_status_routes_correctly() {
3323        let (tool, _conf) = classify("bicameral hemisphere status");
3324        assert_eq!(tool, "bicameral.status");
3325    }
3326
3327    #[test]
3328    fn classify_callosum_routes_to_bicameral_reason() {
3329        let (tool, _conf) = classify("corpus callosum debate perspectives");
3330        assert_eq!(tool, "bicameral.reason");
3331    }
3332
3333    // ── Drive & Emotion (R7) NLU routing tests ──────────────────────
3334
3335    #[test]
3336    fn classify_drive_snapshot_routes_correctly() {
3337        let (tool, _conf) = classify("drive snapshot current motivation state");
3338        assert_eq!(tool, "drive.snapshot");
3339    }
3340
3341    #[test]
3342    fn classify_emotion_routes_to_drive_snapshot() {
3343        let (tool, _conf) = classify("show current emotion and mood");
3344        assert_eq!(tool, "drive.snapshot");
3345    }
3346
3347    #[test]
3348    fn classify_drive_event_routes_correctly() {
3349        let (tool, _conf) = classify("inject drive event reward for success");
3350        assert_eq!(tool, "drive.event");
3351    }
3352
3353    #[test]
3354    fn classify_curiosity_routes_to_drive_snapshot() {
3355        let (tool, _conf) = classify("curiosity satisfaction caution levels");
3356        assert_eq!(tool, "drive.snapshot");
3357    }
3358
3359    // ── Adversarial NLU routing tests ───────────────────────────────
3360
3361    #[test]
3362    fn adversarial_remember_in_redteam_query_doesnt_misroute() {
3363        // "remember" embedded in a redteam query should not route to memory.create
3364        let (tool, _conf) = classify("redteam scan to remember uncovered vectors");
3365        assert_ne!(
3366            tool, "memory.create",
3367            "redteam query should not route to memory.create even with 'remember' embedded"
3368        );
3369    }
3370
3371    #[test]
3372    fn adversarial_delete_in_search_query_doesnt_misroute() {
3373        // "delete" embedded in a search query should not route to memory.delete
3374        let (tool, _conf) = classify("search for memories about delete operations");
3375        assert_ne!(
3376            tool, "memory.delete",
3377            "search query should not route to memory.delete even with 'delete' embedded"
3378        );
3379    }
3380
3381    #[test]
3382    fn adversarial_store_in_gnosis_query_doesnt_misroute() {
3383        // "store" embedded in a gnosis query should not route to memory.create
3384        let (tool, _conf) = classify("explain why the store blocked my action");
3385        assert_ne!(
3386            tool, "memory.create",
3387            "gnosis query should not route to memory.create even with 'store' embedded"
3388        );
3389    }
3390
3391    #[test]
3392    fn adversarial_repeated_keyword_doesnt_inflate_score() {
3393        // Repeating a keyword many times should not artificially inflate the score
3394        let (tool, conf) = classify("remember remember remember remember remember remember");
3395        assert_eq!(tool, "memory.create");
3396        // Confidence should be reasonable, not artificially high from repetition
3397        assert!(
3398            conf <= 1.0,
3399            "repeated keywords should not inflate confidence beyond 1.0: got {conf}"
3400        );
3401    }
3402
3403    #[test]
3404    fn adversarial_keyword_stuffing_doesnt_misroute() {
3405        // Stuffing multiple tool keywords should not cause misrouting
3406        let (tool, _conf) = classify("remember delete search list recall store");
3407        // Should route to one of the memory tools, not error out
3408        assert!(
3409            tool.starts_with("memory."),
3410            "keyword stuffing should still route to a memory tool, got {tool}"
3411        );
3412    }
3413
3414    #[test]
3415    fn adversarial_redteam_with_memory_keyword_doesnt_misroute() {
3416        // "memory" embedded in a redteam query should not route to memory tools
3417        let (tool, _conf) = classify("redteam proposals for memory poisoning attack");
3418        assert_eq!(
3419            tool, "redteam.proposals",
3420            "redteam query should route to redteam.proposals even with 'memory' embedded"
3421        );
3422    }
3423
3424    #[test]
3425    fn adversarial_friction_with_delete_keyword_doesnt_misroute() {
3426        // "friction" with "delete" should route to friction.log, not memory.delete
3427        let (tool, _conf) = classify("log friction about delete operations failing");
3428        // Should route to friction.log due to prefix route, not memory.delete
3429        assert_ne!(
3430            tool, "memory.delete",
3431            "friction query should not route to memory.delete even with 'delete' embedded"
3432        );
3433    }
3434
3435    #[test]
3436    fn adversarial_long_input_doesnt_cascade_misroute() {
3437        // Very long input with many keywords should not cascade into wrong routing
3438        let input = "remember to search for delete and list and recall and store and save \
3439                     and memorize and retrieve and fetch and get and load and access and \
3440                     query and find and look and check and count and purge and forget \
3441                     and remove and drop and clear and wipe and erase and destroy";
3442        let (tool, _conf) = classify(input);
3443        // Should route to some memory tool, not panic or return gnosis
3444        assert!(
3445            tool.starts_with("memory.") || tool == "gnosis",
3446            "long input should route to memory tool or gnosis, got {tool}"
3447        );
3448    }
3449
3450    #[test]
3451    fn adversarial_empty_words_between_keywords() {
3452        // Empty words between keywords should not affect routing
3453        let (tool1, _) = classify("remember the important fact");
3454        let (tool2, _) = classify("remember    the    important    fact");
3455        assert_eq!(tool1, tool2, "extra whitespace should not change routing");
3456    }
3457
3458    #[test]
3459    fn adversarial_unicode_homoglyph_doesnt_misroute() {
3460        // Unicode characters that look like ASCII should not cause misrouting
3461        let (tool, _conf) = classify("rеmеmbеr this fact"); // Cyrillic 'е' chars
3462        // Should NOT route to memory.create because the keywords don't match
3463        // (Cyrillic е ≠ Latin e after tokenization)
3464        assert_ne!(
3465            tool, "memory.create",
3466            "unicode homoglyphs should not trick the router into memory.create"
3467        );
3468    }
3469
3470    // ── Imagination Engine NLU routing tests ─────────────────────────
3471
3472    #[test]
3473    fn classify_imagine_scenarios_routes_to_imagine_scenario() {
3474        let (tool, _conf) = classify("imagine scenarios for improving performance");
3475        assert_eq!(tool, "imagine.scenario");
3476    }
3477
3478    #[test]
3479    fn classify_brainstorm_routes_to_imagine_scenario() {
3480        let (tool, _conf) = classify("brainstorm contingency plans for deployment");
3481        assert_eq!(tool, "imagine.scenario");
3482    }
3483
3484    #[test]
3485    fn classify_envision_routes_to_imagine_scenario() {
3486        let (tool, _conf) = classify("envision what-if possibilities for the system");
3487        assert_eq!(tool, "imagine.scenario");
3488    }
3489
3490    #[test]
3491    fn classify_reflect_routes_to_imagine_reflect() {
3492        let (tool, _conf) =
3493            classify("counterfactual reflect on what should have been done instead");
3494        assert_eq!(tool, "imagine.reflect");
3495    }
3496
3497    #[test]
3498    fn classify_counterfactual_routes_to_imagine_reflect() {
3499        let (tool, _conf) = classify("counterfactual analysis of the decision");
3500        assert_eq!(tool, "imagine.reflect");
3501    }
3502
3503    // ── Property-based tests (proptest) ─────────────────────────────
3504
3505    use proptest::prelude::*;
3506
3507    proptest! {
3508        /// classify() must never panic on arbitrary UTF-8 strings.
3509        #[test]
3510        fn classify_never_panics(text in ".*") {
3511            let (tool, conf) = classify(&text);
3512            prop_assert!(!tool.is_empty(), "tool name must be non-empty");
3513            prop_assert!(
3514                (0.0..=1.0).contains(&conf),
3515                "confidence must be in [0,1], got {conf}"
3516            );
3517        }
3518
3519        /// classify() must never panic on arbitrary bytes (lossy UTF-8).
3520        #[test]
3521        fn classify_never_panics_bytes(data in proptest::collection::vec(any::<u8>(), 0..256)) {
3522            let text = String::from_utf8_lossy(&data);
3523            let (tool, conf) = classify(&text);
3524            prop_assert!(!tool.is_empty());
3525            prop_assert!((0.0..=1.0).contains(&conf));
3526        }
3527
3528        /// classify() is deterministic — same input always yields same output.
3529        #[test]
3530        fn classify_is_deterministic(text in ".*") {
3531            let (tool1, conf1) = classify(&text);
3532            let (tool2, conf2) = classify(&text);
3533            prop_assert_eq!(tool1, tool2);
3534            prop_assert!((conf1 - conf2).abs() < f64::EPSILON);
3535        }
3536
3537        /// Empty or whitespace-only input always returns gnosis with 0.0 confidence.
3538        #[test]
3539        fn classify_empty_returns_gnosis_prop(ws in r"[ \t\n\r]*") {
3540            let (tool, conf) = classify(&ws);
3541            prop_assert_eq!(tool, "gnosis");
3542            prop_assert_eq!(conf, 0.0);
3543        }
3544
3545        /// Confidence is always finite (not NaN or infinity).
3546        #[test]
3547        fn classify_confidence_is_finite(text in ".*") {
3548            let (_, conf) = classify(&text);
3549            prop_assert!(conf.is_finite(), "confidence must be finite, got {conf}");
3550        }
3551    }
3552}