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    let tokens = tokenize(&lower);
2446    if tokens.is_empty() {
2447        return ("gnosis", 0.0, None);
2448    }
2449
2450    let input_tf = term_frequencies(&tokens);
2451
2452    // Curated multi-word intentions are decisive: they are explicit enough
2453    // that profile scoring (which needs lexical overlap) should not veto
2454    // them — "what do you remember about X" shares no keywords with
2455    // memory.search yet is exactly a search.
2456    let probe = lower.trim_start();
2457    if let Some((_, tool, _)) = PHRASE_ROUTES
2458        .iter()
2459        .find(|(phrase, _, _)| probe.starts_with(phrase))
2460    {
2461        return (tool, 1.0, None);
2462    }
2463
2464    // Check for prefix-based routing bonus (single-word command verbs).
2465    let first_word = probe.split_whitespace().next().unwrap_or("");
2466    let prefix_bonus: Option<(&str, f64)> = PREFIX_ROUTES
2467        .iter()
2468        .find(|(verb, _, _)| *verb == first_word)
2469        .map(|(_, tool, bonus)| (*tool, *bonus));
2470
2471    let mut best_tool = "gnosis";
2472    let mut best_score = 0.0;
2473    let mut second: Option<(&'static str, f64)> = None;
2474
2475    for profile in TOOL_PROFILES {
2476        let mut score = cosine_similarity(&input_tf, profile);
2477        // Apply prefix routing: bonus to matching tool, penalty to non-matching
2478        if let Some((bonus_tool, bonus)) = prefix_bonus {
2479            if profile.tool_name == bonus_tool {
2480                score *= bonus;
2481            } else {
2482                // Dampen non-matching tools to respect prefix intent
2483                score /= bonus;
2484            }
2485        }
2486        if score > best_score {
2487            if best_score > 0.0 {
2488                second = Some((best_tool, best_score));
2489            }
2490            best_score = score;
2491            best_tool = profile.tool_name;
2492        } else if score > 0.0
2493            && profile.tool_name != best_tool
2494            && second.is_none_or(|(_, s)| score > s)
2495        {
2496            second = Some((profile.tool_name, score));
2497        }
2498    }
2499
2500    // Minimum confidence threshold — below this, fall back to gnosis. The
2501    // weak top candidate is still disclosed as an alternative so callers
2502    // see "did you mean X?" instead of a bare failure.
2503    const MIN_THRESHOLD: f64 = 0.10;
2504    if best_score < MIN_THRESHOLD {
2505        let alternative = (best_score > 0.0).then_some((best_tool, best_score));
2506        return ("gnosis", 0.0, alternative);
2507    }
2508
2509    (
2510        best_tool,
2511        best_score,
2512        second.filter(|(tool, _)| *tool != best_tool),
2513    )
2514}
2515
2516#[cfg(test)]
2517fn profiled_tools() -> Vec<&'static str> {
2518    TOOL_PROFILES.iter().map(|p| p.tool_name).collect()
2519}
2520
2521#[cfg(test)]
2522fn profile_count() -> usize {
2523    TOOL_PROFILES.len()
2524}
2525
2526#[cfg(test)]
2527mod tests {
2528    use super::*;
2529    use std::collections::HashSet;
2530
2531    #[test]
2532    fn classify_empty_returns_gnosis() {
2533        let (tool, conf) = classify("");
2534        assert_eq!(tool, "gnosis");
2535        assert_eq!(conf, 0.0);
2536    }
2537
2538    #[test]
2539    fn classify_alternative_discloses_a_runner_up() {
2540        // Contract: when candidates compete, the runner-up is disclosed
2541        // (score > 0, different tool) rather than silently dropped; the
2542        // alternative never outranks the top candidate.
2543        let (tool, confidence, alternative) =
2544            classify_with_alternative("recall beta quartz submarine design notes");
2545        assert!(confidence > 0.0, "expected a scored candidate: {tool}");
2546        if let Some((alt_tool, alt_confidence)) = alternative {
2547            assert_ne!(alt_tool, tool, "alternative must be a different tool");
2548            assert!(
2549                alt_confidence <= confidence,
2550                "alternative {alt_tool} ({alt_confidence}) beat {tool} ({confidence})"
2551            );
2552        }
2553    }
2554
2555    #[test]
2556    fn classify_unknown_offers_no_alternative() {
2557        let (tool, confidence, alternative) = classify_with_alternative("zzzqqx vvbnm");
2558        assert_eq!(tool, "gnosis");
2559        assert_eq!(confidence, 0.0);
2560        assert!(
2561            alternative.is_none(),
2562            "no candidate should surface for pure noise: {alternative:?}"
2563        );
2564    }
2565
2566    #[test]
2567    fn classify_whitespace_returns_gnosis() {
2568        let (tool, conf) = classify("   ");
2569        assert_eq!(tool, "gnosis");
2570        assert_eq!(conf, 0.0);
2571    }
2572
2573    #[test]
2574    fn classify_unknown_returns_gnosis() {
2575        let (tool, conf) = classify("xyzzy frobnicate");
2576        assert_eq!(tool, "gnosis");
2577        assert_eq!(conf, 0.0);
2578    }
2579
2580    #[test]
2581    fn classify_remember_routes_to_memory_create() {
2582        let (tool, _conf) = classify("remember that the sky is blue");
2583        assert_eq!(tool, "memory.create");
2584    }
2585
2586    #[test]
2587    fn classify_store_routes_to_memory_create() {
2588        let (tool, _conf) = classify("store this important fact");
2589        assert_eq!(tool, "memory.create");
2590    }
2591
2592    #[test]
2593    fn classify_recall_routes_to_memory_search() {
2594        // Human phrasing: "recall X" is a lookup, not an id read
2595        // (first-run feedback, 2026-09-14).
2596        let (tool, _conf) = classify("recall the last memory");
2597        assert_eq!(tool, "memory.search");
2598    }
2599
2600    #[test]
2601    fn classify_find_routes_to_memory_search() {
2602        let (tool, _conf) = classify("find BETA quartz submarine in memory");
2603        assert_eq!(tool, "memory.search");
2604    }
2605
2606    #[test]
2607    fn classify_what_do_you_remember_routes_to_memory_search() {
2608        let (tool, _conf) = classify("What do you remember about BETA quartz submarine?");
2609        assert_eq!(tool, "memory.search");
2610    }
2611
2612    #[test]
2613    fn classify_what_did_we_decide_routes_to_memory_search() {
2614        let (tool, _conf) = classify("What did we decide about BETA quartz submarine?");
2615        assert_eq!(tool, "memory.search");
2616    }
2617
2618    #[test]
2619    fn classify_look_up_routes_to_memory_search() {
2620        let (tool, _conf) = classify("look up the quartz submarine");
2621        assert_eq!(tool, "memory.search");
2622    }
2623
2624    #[test]
2625    fn classify_search_routes_to_memory_search() {
2626        let (tool, _conf) = classify("search for rust");
2627        assert_eq!(tool, "memory.search");
2628    }
2629
2630    #[test]
2631    fn classify_list_memories_routes_to_memory_list() {
2632        let (tool, _conf) = classify("list memories in codex");
2633        assert_eq!(tool, "memory.list");
2634    }
2635
2636    #[test]
2637    fn classify_delete_memory_routes_to_memory_delete() {
2638        let (tool, _conf) = classify("delete memory abc-123");
2639        assert_eq!(tool, "memory.delete");
2640    }
2641
2642    #[test]
2643    fn classify_karma_routes_to_karma_report() {
2644        let (tool, _conf) = classify("show me the karma report");
2645        assert_eq!(tool, "karma.report");
2646    }
2647
2648    #[test]
2649    fn classify_karma_history_routes_correctly() {
2650        let (tool, _conf) = classify("karma history");
2651        assert_eq!(tool, "karma.history");
2652    }
2653
2654    #[test]
2655    fn classify_dharma_status_routes_correctly() {
2656        let (tool, _conf) = classify("dharma status");
2657        assert_eq!(tool, "dharma.status");
2658    }
2659
2660    #[test]
2661    fn classify_dharma_rules_routes_correctly() {
2662        let (tool, _conf) = classify("show dharma rules");
2663        assert_eq!(tool, "dharma.rules");
2664    }
2665
2666    #[test]
2667    fn classify_harmony_routes_to_harmony_vector() {
2668        let (tool, _conf) = classify("harmony vector status");
2669        assert_eq!(tool, "harmony.vector");
2670    }
2671
2672    #[test]
2673    fn classify_gnosis_explain_routes_correctly() {
2674        let (tool, _conf) = classify("why was my action blocked");
2675        assert_eq!(tool, "gnosis.explain");
2676    }
2677
2678    #[test]
2679    fn classify_session_start_routes_correctly() {
2680        let (tool, _conf) = classify("start session research");
2681        assert_eq!(tool, "session.start");
2682    }
2683
2684    #[test]
2685    fn classify_session_end_routes_correctly() {
2686        let (tool, _conf) = classify("end session abc-123");
2687        assert_eq!(tool, "session.end");
2688    }
2689
2690    #[test]
2691    fn classify_session_continuity_routes_correctly() {
2692        // 9.1.6: continuity phrases must reach session.continuity, not gnosis.
2693        for phrase in [
2694            "what did we decide last time",
2695            "resume where we left off",
2696            "where were we in the previous session",
2697            "continue from where I stopped",
2698            "pick up where we left off",
2699        ] {
2700            let (tool, conf) = classify(phrase);
2701            assert_eq!(tool, "session.continuity", "phrase: {phrase}");
2702            assert!(conf > 0.1, "phrase {phrase} confidence too low: {conf}");
2703        }
2704    }
2705
2706    #[test]
2707    fn classify_session_record_routes_correctly() {
2708        let (tool, _conf) = classify("record this decision for later");
2709        assert_eq!(tool, "session.record");
2710    }
2711
2712    #[test]
2713    fn classify_citta_status_routes_correctly() {
2714        let (tool, _conf) = classify("citta status");
2715        assert_eq!(tool, "citta.status");
2716    }
2717
2718    #[test]
2719    fn classify_dream_trigger_routes_correctly() {
2720        let (tool, _conf) = classify("trigger dream cycle");
2721        assert_eq!(tool, "dream.trigger");
2722    }
2723
2724    #[test]
2725    fn classify_consolidate_routes_correctly() {
2726        let (tool, _conf) = classify("consolidate duplicate memories");
2727        assert_eq!(tool, "memory.consolidate");
2728    }
2729
2730    #[test]
2731    fn classify_emergence_scan_routes_correctly() {
2732        let (tool, _conf) = classify("emergence scan for trending tags");
2733        assert_eq!(tool, "emergence.scan");
2734    }
2735
2736    #[test]
2737    fn classify_spiral_report_routes_correctly() {
2738        let (tool, _conf) = classify("spiral report for autonomy");
2739        assert_eq!(tool, "spiral.report");
2740    }
2741
2742    #[test]
2743    fn classify_retention_prune_routes_correctly() {
2744        let (tool, _conf) = classify("prune memories ready to forget");
2745        assert_eq!(tool, "retention.prune");
2746    }
2747
2748    #[test]
2749    fn classify_tools_list_routes_correctly() {
2750        let (tool, _conf) = classify("list tools");
2751        assert_eq!(tool, "tools.list");
2752    }
2753
2754    #[test]
2755    fn classify_system_health_routes_correctly() {
2756        let (tool, _conf) = classify("system health check");
2757        assert_eq!(tool, "system.health");
2758    }
2759
2760    #[test]
2761    fn classify_agent_register_routes_correctly() {
2762        let (tool, _conf) = classify("register agent worker-1");
2763        assert_eq!(tool, "agent.register");
2764    }
2765
2766    #[test]
2767    fn classify_task_distribute_routes_correctly() {
2768        let (tool, _conf) = classify("distribute task analyze data");
2769        assert_eq!(tool, "task.distribute");
2770    }
2771
2772    #[test]
2773    fn classify_nearby_memories_routes_correctly() {
2774        // "find X" is a search intention taught by the grimoire; the
2775        // specialist nearby route stays reachable without the verb.
2776        let (tool, _conf) = classify("memories nearby");
2777        assert_eq!(tool, "memory.nearby");
2778    }
2779
2780    #[test]
2781    fn classify_hybrid_recall_routes_correctly() {
2782        let (tool, _conf) = classify("hybrid recall for rust");
2783        assert_eq!(tool, "memory.hybrid_recall");
2784    }
2785
2786    #[test]
2787    fn classify_galaxy_stats_routes_correctly() {
2788        let (tool, _conf) = classify("galaxy stats overview");
2789        assert_eq!(tool, "galaxy.stats");
2790    }
2791
2792    #[test]
2793    fn classify_galaxy_export_routes_correctly() {
2794        let (tool, _conf) = classify("export galaxy backup");
2795        assert_eq!(tool, "galaxy.export");
2796    }
2797
2798    #[test]
2799    fn classify_kg_extract_routes_correctly() {
2800        let (tool, _conf) = classify("extract entities knowledge graph");
2801        assert_eq!(tool, "kg.extract");
2802    }
2803
2804    #[test]
2805    fn classify_kg_query_routes_correctly() {
2806        let (tool, _conf) = classify("knowledge graph query relationships");
2807        assert_eq!(tool, "kg.query");
2808    }
2809
2810    #[test]
2811    fn classify_kg_top_routes_correctly() {
2812        let (tool, _conf) = classify("top hub nodes knowledge graph");
2813        assert_eq!(tool, "kg.top");
2814    }
2815
2816    #[test]
2817    fn classify_graph_walk_routes_correctly() {
2818        let (tool, _conf) = classify("traverse graph walk bfs");
2819        assert_eq!(tool, "graph.walk");
2820    }
2821
2822    #[test]
2823    fn classify_graph_community_routes_correctly() {
2824        let (tool, _conf) = classify("detect communities clusters in graph");
2825        assert_eq!(tool, "graph.community");
2826    }
2827
2828    #[test]
2829    fn classify_graph_propagate_routes_correctly() {
2830        let (tool, _conf) = classify("propagate activation spread ripple");
2831        assert_eq!(tool, "graph.propagate");
2832    }
2833
2834    #[test]
2835    fn classify_galaxy_transfer_routes_correctly() {
2836        let (tool, _conf) = classify("transfer move memories galaxy");
2837        assert_eq!(tool, "galaxy.transfer");
2838    }
2839
2840    #[test]
2841    fn classify_galaxy_merge_routes_correctly() {
2842        let (tool, _conf) = classify("merge combine galaxies");
2843        assert_eq!(tool, "galaxy.merge");
2844    }
2845
2846    #[test]
2847    fn classify_galaxy_snapshot_routes_correctly() {
2848        let (tool, _conf) = classify("snapshot backup galaxy");
2849        assert_eq!(tool, "galaxy.snapshot");
2850    }
2851
2852    #[test]
2853    fn classify_galaxy_restore_routes_correctly() {
2854        let (tool, _conf) = classify("restore recover galaxy snapshot");
2855        assert_eq!(tool, "galaxy.restore");
2856    }
2857
2858    #[test]
2859    fn classify_agent_trust_routes_correctly() {
2860        let (tool, _conf) = classify("trust reliability agent score");
2861        assert_eq!(tool, "agent.trust");
2862    }
2863
2864    #[test]
2865    fn classify_agent_descriptions_routes_correctly() {
2866        let (tool, _conf) = classify("describe agent profile info");
2867        assert_eq!(tool, "agent.descriptions");
2868    }
2869
2870    #[test]
2871    fn classify_agent_capabilities_routes_correctly() {
2872        let (tool, _conf) = classify("agent capabilities skills abilities");
2873        assert_eq!(tool, "agent.capabilities");
2874    }
2875
2876    #[test]
2877    fn classify_agent_heartbeat_history_routes_correctly() {
2878        let (tool, _conf) = classify("heartbeat history log agent");
2879        assert_eq!(tool, "agent.heartbeat.history");
2880    }
2881
2882    #[test]
2883    fn classify_agent_deregister_routes_correctly() {
2884        let (tool, _conf) = classify("deregister unregister remove agent");
2885        assert_eq!(tool, "agent.deregister");
2886    }
2887
2888    #[test]
2889    fn classify_galaxy_dashboard_routes_correctly() {
2890        let (tool, _conf) = classify("galaxy dashboard overview panel");
2891        assert_eq!(tool, "galaxy.dashboard");
2892    }
2893
2894    #[test]
2895    fn classify_galaxy_backup_routes_correctly() {
2896        let (tool, _conf) = classify("backup archive galaxy dump");
2897        assert_eq!(tool, "galaxy.backup");
2898    }
2899
2900    #[test]
2901    fn classify_galaxy_taxonomy_routes_correctly() {
2902        let (tool, _conf) = classify("galaxy taxonomy classification categories");
2903        assert_eq!(tool, "galaxy.taxonomy");
2904    }
2905
2906    #[test]
2907    fn classify_galaxy_purge_routes_correctly() {
2908        let (tool, _conf) = classify("purge wipe clear galaxy");
2909        assert_eq!(tool, "galaxy.purge");
2910    }
2911
2912    #[test]
2913    fn classify_galaxy_health_routes_correctly() {
2914        let (tool, _conf) = classify("galaxy health diagnostic checkup");
2915        assert_eq!(tool, "galaxy.health");
2916    }
2917
2918    #[test]
2919    fn classify_memory_sort_routes_correctly() {
2920        let (tool, _conf) = classify("sort memories by importance");
2921        assert_eq!(tool, "memory.sort");
2922    }
2923
2924    #[test]
2925    fn classify_memory_filter_routes_correctly() {
2926        let (tool, _conf) = classify("filter memories by tag criteria");
2927        assert_eq!(tool, "memory.filter");
2928    }
2929
2930    #[test]
2931    fn classify_memory_deduplicate_routes_correctly() {
2932        let (tool, _conf) = classify("deduplicate memories redundant duplicate");
2933        assert_eq!(tool, "memory.deduplicate");
2934    }
2935
2936    #[test]
2937    fn classify_memory_export_routes_correctly() {
2938        let (tool, _conf) = classify("export memories csv format download");
2939        assert_eq!(tool, "memory.export");
2940    }
2941
2942    #[test]
2943    fn classify_homeostasis_check_routes_correctly() {
2944        let (tool, _conf) = classify("homeostasis check balance vitals metrics");
2945        assert_eq!(tool, "homeostasis.check");
2946    }
2947
2948    #[test]
2949    fn classify_homeostasis_adjust_routes_correctly() {
2950        let (tool, _conf) = classify("homeostasis adjust rebalance weight tune");
2951        assert_eq!(tool, "homeostasis.adjust");
2952    }
2953
2954    #[test]
2955    fn classify_homeostasis_history_routes_correctly() {
2956        let (tool, _conf) = classify("homeostasis history trend past samples");
2957        assert_eq!(tool, "homeostasis.history");
2958    }
2959
2960    #[test]
2961    fn classify_homeostasis_alerts_routes_correctly() {
2962        let (tool, _conf) = classify("homeostasis alerts warning critical threshold");
2963        assert_eq!(tool, "homeostasis.alerts");
2964    }
2965
2966    #[test]
2967    fn classify_reflex_dispatch_routes_correctly() {
2968        let (tool, _conf) = classify("dispatch reflex e_stop emergency handler");
2969        assert_eq!(tool, "reflex.dispatch");
2970    }
2971
2972    #[test]
2973    fn classify_reflex_status_routes_correctly() {
2974        let (tool, _conf) = classify("reflex status table registered handlers");
2975        assert_eq!(tool, "reflex.status");
2976    }
2977
2978    #[test]
2979    fn classify_workspace_spotlight_routes_correctly() {
2980        let (tool, _conf) = classify("workspace spotlight attention arbitration");
2981        assert_eq!(tool, "workspace.spotlight");
2982    }
2983
2984    #[test]
2985    fn classify_workspace_events_routes_correctly() {
2986        let (tool, _conf) = classify("workspace recent events backlog history");
2987        assert_eq!(tool, "workspace.events");
2988    }
2989
2990    #[test]
2991    fn classify_workspace_publish_routes_correctly() {
2992        let (tool, _conf) = classify("publish broadcast workspace event emit");
2993        assert_eq!(tool, "workspace.publish");
2994    }
2995
2996    #[test]
2997    fn classify_workspace_stats_routes_correctly() {
2998        let (tool, _conf) = classify("workspace stats statistics transfers count");
2999        assert_eq!(tool, "workspace.stats");
3000    }
3001
3002    #[test]
3003    fn classify_timescale_status_routes_correctly() {
3004        let (tool, _conf) = classify("timescale status tier bus brain_wave active");
3005        assert_eq!(tool, "timescale.status");
3006    }
3007
3008    #[test]
3009    fn classify_timescale_hooks_routes_correctly() {
3010        let (tool, _conf) = classify("timescale hooks list tick timeout performance");
3011        assert_eq!(tool, "timescale.hooks");
3012    }
3013
3014    #[test]
3015    fn classify_confidence_is_reasonable() {
3016        let (_tool, conf) = classify("remember that rust is fast");
3017        assert!(
3018            conf > 0.15,
3019            "confidence should be > 0.15 for clear match, got {conf}"
3020        );
3021    }
3022
3023    #[test]
3024    fn classify_case_insensitive() {
3025        let (tool1, _) = classify("REMEMBER THAT");
3026        let (tool2, _) = classify("remember that");
3027        assert_eq!(tool1, tool2);
3028    }
3029
3030    #[test]
3031    fn classify_partial_match_works() {
3032        let (tool, conf) = classify("search rust");
3033        assert_eq!(tool, "memory.search");
3034        assert!(conf > 0.0);
3035    }
3036
3037    #[test]
3038    fn classify_multi_word_query() {
3039        let (tool, _conf) = classify("show me the effectiveness report for tools");
3040        assert_eq!(tool, "tools.effectiveness_report");
3041    }
3042
3043    #[test]
3044    fn profile_count_is_reasonable() {
3045        // Should have 50+ profiles
3046        assert!(
3047            profile_count() >= 60,
3048            "expected 60+ profiles, got {}",
3049            profile_count()
3050        );
3051    }
3052
3053    #[test]
3054    fn profiled_tools_are_unique() {
3055        let tools = profiled_tools();
3056        let set: HashSet<&str> = tools.iter().copied().collect();
3057        assert_eq!(tools.len(), set.len(), "duplicate tool names in profiles");
3058    }
3059
3060    #[test]
3061    fn classify_unique_patterns_count() {
3062        let inputs = [
3063            "remember",
3064            "recall",
3065            "list memories",
3066            "delete memory",
3067            "search",
3068            "query",
3069            "associate",
3070            "associations",
3071            "consolidate",
3072            "decay",
3073            "batch read",
3074            "update memory",
3075            "tag memory",
3076            "memory stats",
3077            "hybrid recall",
3078            "count memories",
3079            "list tags",
3080            "mine associations",
3081            "start session",
3082            "checkpoint",
3083            "recall session",
3084            "end session",
3085            "list sessions",
3086            "citta status",
3087            "reflect",
3088            "coherence",
3089            "dream status",
3090            "trigger dream",
3091            "effectiveness",
3092            "retire tool",
3093            "pattern search",
3094            "salience",
3095            "serendipity",
3096            "detect clusters",
3097            "list constellations",
3098            "galaxy stats",
3099            "export galaxy",
3100            "import galaxy",
3101            "karma",
3102            "karma history",
3103            "clear karma",
3104            "dharma rules",
3105            "dharma audit",
3106            "dharma profiles",
3107            "dharma",
3108            "register agent",
3109            "list agents",
3110            "heartbeat",
3111            "distribute task",
3112            "task status",
3113            "system health",
3114            "system config",
3115            "flush",
3116            "tools",
3117            "nearby memories",
3118            "vector search",
3119            "find similar",
3120            "extract entities knowledge graph",
3121            "knowledge graph query",
3122            "top hub nodes",
3123            "traverse graph walk",
3124            "detect communities",
3125            "propagate activation",
3126            "transfer galaxy",
3127            "merge galaxies",
3128            "snapshot galaxy",
3129            "restore galaxy",
3130            "sort memories",
3131            "filter memories",
3132            "deduplicate memories",
3133            "export memories csv",
3134            "homeostasis check",
3135            "homeostasis adjust",
3136            "homeostasis history",
3137            "homeostasis alerts",
3138            "dispatch reflex e_stop",
3139            "reflex status table",
3140            "workspace spotlight attention",
3141            "workspace recent events",
3142            "publish workspace event",
3143            "workspace stats summary",
3144            "timescale status tiers",
3145            "timescale hooks tick",
3146        ];
3147        let mut tools: HashSet<&str> = HashSet::new();
3148        for input in &inputs {
3149            let (tool, _) = classify(input);
3150            tools.insert(tool);
3151        }
3152        assert!(
3153            tools.len() >= 30,
3154            "Expected 30+ unique NLU targets, got {}",
3155            tools.len()
3156        );
3157    }
3158
3159    #[test]
3160    fn classify_vector_search_routes_correctly() {
3161        let (tool, conf) = classify("vector search similar memories");
3162        assert_eq!(tool, "memory.vector.search");
3163        assert!(conf > 0.0);
3164    }
3165
3166    #[test]
3167    fn classify_embedding_search_routes_correctly() {
3168        let (tool, conf) = classify("embedding similarity lookup");
3169        assert_eq!(tool, "memory.vector.search");
3170        assert!(conf > 0.0);
3171    }
3172
3173    #[test]
3174    fn classify_semantic_search_routes_correctly() {
3175        let (tool, conf) = classify("semantic similarity search");
3176        assert_eq!(tool, "memory.vector.search");
3177        assert!(conf > 0.0);
3178    }
3179
3180    #[test]
3181    fn classify_stemming_handles_morphological_variants() {
3182        // -ing form should route same as base
3183        let (tool1, _) = classify("searching for rust");
3184        let (tool2, _) = classify("search for rust");
3185        assert_eq!(tool1, tool2);
3186
3187        // -ed form
3188        let (tool3, _) = classify("stored important fact");
3189        let (tool4, _) = classify("store important fact");
3190        assert_eq!(tool3, tool4);
3191
3192        // plural → singular
3193        let (tool5, _) = classify("list memories");
3194        let (tool6, _) = classify("list memory");
3195        assert_eq!(tool5, tool6);
3196    }
3197
3198    #[test]
3199    fn stem_handles_unicode_without_panicking() {
3200        // Regression: the -ing double-consonant check sliced by byte index,
3201        // panicking on words whose base ends in a multi-byte character.
3202        assert_eq!(stem("xéing"), "xé");
3203        assert_eq!(stem("éing"), "é");
3204        assert_eq!(stem("caféing"), "café");
3205        // ASCII behavior unchanged
3206        assert_eq!(stem("running"), "run");
3207        assert_eq!(stem("swimming"), "swim");
3208        assert_eq!(stem("searching"), "search");
3209        assert_eq!(stem("typing"), "typ");
3210        assert_eq!(stem("memories"), "memory");
3211        assert_eq!(stem("stored"), "store");
3212    }
3213
3214    #[test]
3215    fn classify_handles_unicode_thoughts_without_panicking() {
3216        // Full pipeline: tokenize → stem on multibyte input must not panic.
3217        let (tool, _conf) = classify("caféing sur les mémoires");
3218        assert!(!tool.is_empty());
3219        let (tool2, _conf2) = classify("mémoire éing recherche");
3220        assert!(!tool2.is_empty());
3221    }
3222
3223    #[test]
3224    fn classify_confidence_improved_with_stopwords() {
3225        // With stopwords filtered, confidence should be higher
3226        let (_, conf) = classify("remember that rust is fast");
3227        assert!(
3228            conf > 0.20,
3229            "confidence should be > 0.20 with stopword filtering, got {conf}"
3230        );
3231    }
3232
3233    // ── Self-model (R4) NLU routing tests ───────────────────────────
3234
3235    #[test]
3236    fn classify_forecast_routes_to_selfmodel_forecast() {
3237        let (tool, _conf) = classify("forecast cpu load for next 5 samples");
3238        assert_eq!(tool, "selfmodel.forecast");
3239    }
3240
3241    #[test]
3242    fn classify_predict_routes_to_selfmodel_forecast() {
3243        let (tool, _conf) = classify("predict memory pressure trend");
3244        assert_eq!(tool, "selfmodel.forecast");
3245    }
3246
3247    #[test]
3248    fn classify_alerts_routes_to_selfmodel_alerts() {
3249        let (tool, _conf) = classify("selfmodel alerts");
3250        assert_eq!(tool, "selfmodel.alerts");
3251    }
3252
3253    #[test]
3254    fn classify_warning_routes_to_selfmodel_alerts() {
3255        let (tool, _conf) = classify("selfmodel critical warnings");
3256        assert_eq!(tool, "selfmodel.alerts");
3257    }
3258
3259    #[test]
3260    fn classify_snapshot_routes_to_selfmodel_snapshot() {
3261        let (tool, _conf) = classify("selfmodel snapshot");
3262        assert_eq!(tool, "selfmodel.snapshot");
3263    }
3264
3265    #[test]
3266    fn classify_introspection_routes_to_selfmodel_snapshot() {
3267        let (tool, _conf) = classify("show introspection state overview");
3268        assert_eq!(tool, "selfmodel.snapshot");
3269    }
3270
3271    // ── Bicameral (R5) NLU routing tests ────────────────────────────
3272
3273    #[test]
3274    fn classify_bicameral_debate_routes_to_bicameral_reason() {
3275        let (tool, _conf) = classify("bicameral debate on rust vs python");
3276        assert_eq!(tool, "bicameral.reason");
3277    }
3278
3279    #[test]
3280    fn classify_hemisphere_consensus_routes_to_bicameral_reason() {
3281        let (tool, _conf) = classify("dual hemisphere consensus deliberation");
3282        assert_eq!(tool, "bicameral.reason");
3283    }
3284
3285    #[test]
3286    fn classify_bicameral_status_routes_correctly() {
3287        let (tool, _conf) = classify("bicameral hemisphere status");
3288        assert_eq!(tool, "bicameral.status");
3289    }
3290
3291    #[test]
3292    fn classify_callosum_routes_to_bicameral_reason() {
3293        let (tool, _conf) = classify("corpus callosum debate perspectives");
3294        assert_eq!(tool, "bicameral.reason");
3295    }
3296
3297    // ── Drive & Emotion (R7) NLU routing tests ──────────────────────
3298
3299    #[test]
3300    fn classify_drive_snapshot_routes_correctly() {
3301        let (tool, _conf) = classify("drive snapshot current motivation state");
3302        assert_eq!(tool, "drive.snapshot");
3303    }
3304
3305    #[test]
3306    fn classify_emotion_routes_to_drive_snapshot() {
3307        let (tool, _conf) = classify("show current emotion and mood");
3308        assert_eq!(tool, "drive.snapshot");
3309    }
3310
3311    #[test]
3312    fn classify_drive_event_routes_correctly() {
3313        let (tool, _conf) = classify("inject drive event reward for success");
3314        assert_eq!(tool, "drive.event");
3315    }
3316
3317    #[test]
3318    fn classify_curiosity_routes_to_drive_snapshot() {
3319        let (tool, _conf) = classify("curiosity satisfaction caution levels");
3320        assert_eq!(tool, "drive.snapshot");
3321    }
3322
3323    // ── Adversarial NLU routing tests ───────────────────────────────
3324
3325    #[test]
3326    fn adversarial_remember_in_redteam_query_doesnt_misroute() {
3327        // "remember" embedded in a redteam query should not route to memory.create
3328        let (tool, _conf) = classify("redteam scan to remember uncovered vectors");
3329        assert_ne!(
3330            tool, "memory.create",
3331            "redteam query should not route to memory.create even with 'remember' embedded"
3332        );
3333    }
3334
3335    #[test]
3336    fn adversarial_delete_in_search_query_doesnt_misroute() {
3337        // "delete" embedded in a search query should not route to memory.delete
3338        let (tool, _conf) = classify("search for memories about delete operations");
3339        assert_ne!(
3340            tool, "memory.delete",
3341            "search query should not route to memory.delete even with 'delete' embedded"
3342        );
3343    }
3344
3345    #[test]
3346    fn adversarial_store_in_gnosis_query_doesnt_misroute() {
3347        // "store" embedded in a gnosis query should not route to memory.create
3348        let (tool, _conf) = classify("explain why the store blocked my action");
3349        assert_ne!(
3350            tool, "memory.create",
3351            "gnosis query should not route to memory.create even with 'store' embedded"
3352        );
3353    }
3354
3355    #[test]
3356    fn adversarial_repeated_keyword_doesnt_inflate_score() {
3357        // Repeating a keyword many times should not artificially inflate the score
3358        let (tool, conf) = classify("remember remember remember remember remember remember");
3359        assert_eq!(tool, "memory.create");
3360        // Confidence should be reasonable, not artificially high from repetition
3361        assert!(
3362            conf <= 1.0,
3363            "repeated keywords should not inflate confidence beyond 1.0: got {conf}"
3364        );
3365    }
3366
3367    #[test]
3368    fn adversarial_keyword_stuffing_doesnt_misroute() {
3369        // Stuffing multiple tool keywords should not cause misrouting
3370        let (tool, _conf) = classify("remember delete search list recall store");
3371        // Should route to one of the memory tools, not error out
3372        assert!(
3373            tool.starts_with("memory."),
3374            "keyword stuffing should still route to a memory tool, got {tool}"
3375        );
3376    }
3377
3378    #[test]
3379    fn adversarial_redteam_with_memory_keyword_doesnt_misroute() {
3380        // "memory" embedded in a redteam query should not route to memory tools
3381        let (tool, _conf) = classify("redteam proposals for memory poisoning attack");
3382        assert_eq!(
3383            tool, "redteam.proposals",
3384            "redteam query should route to redteam.proposals even with 'memory' embedded"
3385        );
3386    }
3387
3388    #[test]
3389    fn adversarial_friction_with_delete_keyword_doesnt_misroute() {
3390        // "friction" with "delete" should route to friction.log, not memory.delete
3391        let (tool, _conf) = classify("log friction about delete operations failing");
3392        // Should route to friction.log due to prefix route, not memory.delete
3393        assert_ne!(
3394            tool, "memory.delete",
3395            "friction query should not route to memory.delete even with 'delete' embedded"
3396        );
3397    }
3398
3399    #[test]
3400    fn adversarial_long_input_doesnt_cascade_misroute() {
3401        // Very long input with many keywords should not cascade into wrong routing
3402        let input = "remember to search for delete and list and recall and store and save \
3403                     and memorize and retrieve and fetch and get and load and access and \
3404                     query and find and look and check and count and purge and forget \
3405                     and remove and drop and clear and wipe and erase and destroy";
3406        let (tool, _conf) = classify(input);
3407        // Should route to some memory tool, not panic or return gnosis
3408        assert!(
3409            tool.starts_with("memory.") || tool == "gnosis",
3410            "long input should route to memory tool or gnosis, got {tool}"
3411        );
3412    }
3413
3414    #[test]
3415    fn adversarial_empty_words_between_keywords() {
3416        // Empty words between keywords should not affect routing
3417        let (tool1, _) = classify("remember the important fact");
3418        let (tool2, _) = classify("remember    the    important    fact");
3419        assert_eq!(tool1, tool2, "extra whitespace should not change routing");
3420    }
3421
3422    #[test]
3423    fn adversarial_unicode_homoglyph_doesnt_misroute() {
3424        // Unicode characters that look like ASCII should not cause misrouting
3425        let (tool, _conf) = classify("rеmеmbеr this fact"); // Cyrillic 'е' chars
3426        // Should NOT route to memory.create because the keywords don't match
3427        // (Cyrillic е ≠ Latin e after tokenization)
3428        assert_ne!(
3429            tool, "memory.create",
3430            "unicode homoglyphs should not trick the router into memory.create"
3431        );
3432    }
3433
3434    // ── Imagination Engine NLU routing tests ─────────────────────────
3435
3436    #[test]
3437    fn classify_imagine_scenarios_routes_to_imagine_scenario() {
3438        let (tool, _conf) = classify("imagine scenarios for improving performance");
3439        assert_eq!(tool, "imagine.scenario");
3440    }
3441
3442    #[test]
3443    fn classify_brainstorm_routes_to_imagine_scenario() {
3444        let (tool, _conf) = classify("brainstorm contingency plans for deployment");
3445        assert_eq!(tool, "imagine.scenario");
3446    }
3447
3448    #[test]
3449    fn classify_envision_routes_to_imagine_scenario() {
3450        let (tool, _conf) = classify("envision what-if possibilities for the system");
3451        assert_eq!(tool, "imagine.scenario");
3452    }
3453
3454    #[test]
3455    fn classify_reflect_routes_to_imagine_reflect() {
3456        let (tool, _conf) =
3457            classify("counterfactual reflect on what should have been done instead");
3458        assert_eq!(tool, "imagine.reflect");
3459    }
3460
3461    #[test]
3462    fn classify_counterfactual_routes_to_imagine_reflect() {
3463        let (tool, _conf) = classify("counterfactual analysis of the decision");
3464        assert_eq!(tool, "imagine.reflect");
3465    }
3466
3467    // ── Property-based tests (proptest) ─────────────────────────────
3468
3469    use proptest::prelude::*;
3470
3471    proptest! {
3472        /// classify() must never panic on arbitrary UTF-8 strings.
3473        #[test]
3474        fn classify_never_panics(text in ".*") {
3475            let (tool, conf) = classify(&text);
3476            prop_assert!(!tool.is_empty(), "tool name must be non-empty");
3477            prop_assert!(
3478                (0.0..=1.0).contains(&conf),
3479                "confidence must be in [0,1], got {conf}"
3480            );
3481        }
3482
3483        /// classify() must never panic on arbitrary bytes (lossy UTF-8).
3484        #[test]
3485        fn classify_never_panics_bytes(data in proptest::collection::vec(any::<u8>(), 0..256)) {
3486            let text = String::from_utf8_lossy(&data);
3487            let (tool, conf) = classify(&text);
3488            prop_assert!(!tool.is_empty());
3489            prop_assert!((0.0..=1.0).contains(&conf));
3490        }
3491
3492        /// classify() is deterministic — same input always yields same output.
3493        #[test]
3494        fn classify_is_deterministic(text in ".*") {
3495            let (tool1, conf1) = classify(&text);
3496            let (tool2, conf2) = classify(&text);
3497            prop_assert_eq!(tool1, tool2);
3498            prop_assert!((conf1 - conf2).abs() < f64::EPSILON);
3499        }
3500
3501        /// Empty or whitespace-only input always returns gnosis with 0.0 confidence.
3502        #[test]
3503        fn classify_empty_returns_gnosis_prop(ws in r"[ \t\n\r]*") {
3504            let (tool, conf) = classify(&ws);
3505            prop_assert_eq!(tool, "gnosis");
3506            prop_assert_eq!(conf, 0.0);
3507        }
3508
3509        /// Confidence is always finite (not NaN or infinity).
3510        #[test]
3511        fn classify_confidence_is_finite(text in ".*") {
3512            let (_, conf) = classify(&text);
3513            prop_assert!(conf.is_finite(), "confidence must be finite, got {conf}");
3514        }
3515    }
3516}