#![forbid(unsafe_code)]
#![allow(clippy::significant_drop_tightening)]
pub mod embedding_router;
pub mod expansion;
pub mod nlu;
pub mod profiles;
pub use expansion::lkep::{
LkepError, LkepExecTool, decode_lkep, parse_lkep_expression, primary_arg_for_route,
resolve_arg, resolve_route,
};
use async_trait::async_trait;
use std::sync::Arc;
use serde_json::{Value, json};
use wm_cognitive::GanYingBus;
use wm_core::{
Capability, Context, EffectRow, EpisodicCapturePolicy, EpisodicKind, EpisodicRecord, Galaxy,
Gana, Provenance, ProvenanceSource, Resource, Tool, ToolStats,
};
use wm_dispatch::{DispatchPipeline, ToolRegistry, ToolRegistryBuilder};
use wm_governance::{DharmaGate, KarmaLedger, ResourceRules};
use wm_memory::{
Association, AssociationStore, ConversationalSearch, Memory, MemoryQuery, MemoryStore,
RecallEngine, SearchEngine, VectorStore,
};
use wm_substrate::SubstrateMonitor;
use wm_substrate::anomaly::AnomalyDetector;
use wm_substrate::homeostatic::HomeostaticLoop;
use wm_substrate::sensorimotor::{ReflexLoop, SensorimotorBus};
use crate::expansion::common::{
bool_prop, fresh_write_galaxies, int_prop, memory_galaxy_reads, memory_galaxy_writes, num_prop,
schema, str_array_prop, str_prop,
};
pub(crate) const GLYPH_ROUTES: &[(&str, &str)] = &[
("memory.search", "Ms"),
("memory.create", "Mc"),
("memory.read", "Mr"),
("memory.hybrid_recall", "Mh"),
("memory.list", "Ml"),
("session.record", "Sr"),
("session.continuity", "Sc"),
("session.checkpoint", "Sk"),
("dharma.escalate", "De"),
("dharma.review_queue", "Dq"),
("dharma.resolve_review", "Dr"),
("dharma.rules", "Du"),
("graph.walk", "Gw"),
("citta.status", "Cs"),
("dream.status", "Ds"),
("smarana.status", "Sm"),
("tools.list", "Tl"),
("agent.list", "Al"),
("karma.report", "Kr"),
("memory.search", "忆"),
("memory.search", "索"),
("memory.create", "录"),
("memory.create", "存"),
("memory.read", "读"),
("memory.hybrid_recall", "回"),
("session.continuity", "续"),
("session.checkpoint", "契"),
("session.record", "记"),
("citta.status", "心"),
("dharma.rules", "律"),
("karma.report", "业"),
("tools.list", "具"),
];
pub(crate) const GLYPH_ARGS: &[(&str, &str)] = &[
("route", "r"),
("args", "a"),
("query", "q"),
("limit", "n"),
("content", "c"),
("id", "i"),
("tags", "t"),
("title", "h"),
("session_id", "s"),
("role", "o"),
("turn_type", "y"),
("importance", "p"),
("tool", "T"),
("action", "N"),
("purpose", "u"),
("decision", "d"),
("score", "e"),
("depth", "D"),
("scope", "S"),
("name", "m"),
("arguments", "g"),
("query", "问"),
("query", "寻"),
("limit", "数"),
("content", "文"),
("tags", "标"),
("scope", "界"),
("id", "号"),
];
#[must_use]
pub fn glyph_mode_from_env() -> bool {
std::env::var("WM_GLYPH").is_ok_and(|v| v == "1" || v.eq_ignore_ascii_case("true"))
}
pub(crate) fn glyph_lookup<'a>(book: &'a [(&'a str, &'a str)], from: &str) -> Option<&'a str> {
book.iter().find(|(k, _)| *k == from).map(|(_, code)| *code)
}
pub(crate) fn glyph_reverse<'a>(book: &'a [(&'a str, &'a str)], code: &str) -> Option<&'a str> {
book.iter().find(|(_, v)| *v == code).map(|(k, _)| *k)
}
#[must_use]
pub fn decode_glyph(args: &Value) -> Option<Value> {
let obj = args.as_object()?;
let rcode = obj.get("r")?.as_str()?;
let route = glyph_reverse(GLYPH_ROUTES, rcode)?;
let mut out = serde_json::Map::new();
out.insert("route".into(), Value::String(route.to_string()));
let a = obj.get("a").cloned().unwrap_or_else(|| json!({}));
if let Some(aobj) = a.as_object() {
let mut decoded = serde_json::Map::new();
for (k, v) in aobj {
let name = glyph_reverse(GLYPH_ARGS, k).unwrap_or(k);
decoded.insert(name.to_string(), v.clone());
}
out.insert("args".into(), Value::Object(decoded));
}
Some(Value::Object(out))
}
#[must_use]
pub fn encode_glyph(route: &str, args: &Value) -> Value {
let code = glyph_lookup(GLYPH_ROUTES, route).unwrap_or(route);
let mut a = serde_json::Map::new();
if let Some(obj) = args.as_object() {
for (k, v) in obj {
let kc = glyph_lookup(GLYPH_ARGS, k).unwrap_or(k);
a.insert(kc.to_string(), v.clone());
}
}
json!({ "r": code, "a": Value::Object(a) })
}
const NLU_LOW_CONFIDENCE: f64 = 0.30;
const NLU_ABSTENTION_THRESHOLD: f64 = 0.15;
fn capture_explicit_memory(
store: &MemoryStore,
memory: &Memory,
kind: EpisodicKind,
source: ProvenanceSource,
session_id: Option<uuid::Uuid>,
sequence: u64,
) -> Option<String> {
let record = explicit_memory_record(memory, kind, source, session_id, sequence);
match store
.episodic()
.append_explicit(&record, EpisodicCapturePolicy::explicit_only())
{
Ok(_) => None,
Err(error) => {
tracing::warn!(
memory_id = %memory.metadata.id,
"episodic capture failed after legacy write: {error}"
);
Some(error.to_string())
}
}
}
fn explicit_memory_record(
memory: &Memory,
kind: EpisodicKind,
source: ProvenanceSource,
session_id: Option<uuid::Uuid>,
sequence: u64,
) -> EpisodicRecord {
let resolved_kind = resolve_episodic_kind(memory, kind);
EpisodicRecord::new(
session_id,
sequence,
resolved_kind,
memory.content.clone(),
Provenance::new(source),
)
.with_id(memory.metadata.id)
.with_visibility(memory.metadata.is_private, memory.metadata.model_exclude)
}
fn resolve_episodic_kind(memory: &Memory, default: EpisodicKind) -> EpisodicKind {
let tags = &memory.metadata.tags;
if tags.iter().any(|t| t == "user") {
EpisodicKind::UserStatement
} else if tags.iter().any(|t| t == "assistant") {
EpisodicKind::AssistantResponse
} else {
default
}
}
fn capture_explicit_memories(
store: &MemoryStore,
memories: &[(Galaxy, Memory)],
kind: EpisodicKind,
source: ProvenanceSource,
session_id: Option<uuid::Uuid>,
) -> Option<String> {
if memories.is_empty() {
return None;
}
let records: Vec<EpisodicRecord> = memories
.iter()
.enumerate()
.map(|(sequence, (_, memory))| {
explicit_memory_record(memory, kind, source, session_id, sequence as u64)
})
.collect();
match store
.episodic()
.append_explicit_batch(&records, EpisodicCapturePolicy::explicit_only())
{
Ok(_) => None,
Err(error) => {
tracing::warn!("episodic batch capture failed after legacy write: {error}");
Some(error.to_string())
}
}
}
fn attach_episodic_capture_warning(response: &mut Value, error: Option<String>) {
let Some(error) = error else { return };
let message = format!(
"episodic capture failed after the memory was stored — episodic recall will not see it: {error}"
);
match response.get_mut("warnings").and_then(Value::as_array_mut) {
Some(list) => list.push(Value::String(message)),
None => response["warnings"] = json!([message]),
}
}
fn attestation_agent_id(ctx: &Context) -> String {
ctx.session_id
.map(|u| u.to_string())
.or_else(|| ctx.user_id.clone())
.unwrap_or_else(|| "local".to_string())
}
fn node_attestation_key() -> Option<String> {
std::env::var(wm_memory::attestation::ATTESTATION_KEY_ENV)
.ok()
.filter(|k| !k.trim().is_empty())
}
fn attest_created_memory(
store: &MemoryStore,
galaxy: Galaxy,
id: uuid::Uuid,
record_hash: &str,
ctx: &Context,
key_hex: Option<&str>,
) -> (bool, Option<String>) {
let key_hex = match key_hex {
Some(k) if !k.trim().is_empty() => k,
_ => return (false, Some("node key unavailable".to_string())),
};
let agent_id = attestation_agent_id(ctx);
let timestamp = wm_core::time::now_unix_secs();
let payload = wm_memory::attestation::attestation_payload(
galaxy.db_name(),
&id.to_string(),
record_hash,
&agent_id,
timestamp,
);
let Some((public_key_hex, signature_hex)) =
wm_memory::attestation::sign_attestation_from_root(&payload, key_hex)
else {
tracing::warn!("creation attestation skipped for memory {id}: key material invalid");
return (false, Some("node key invalid".to_string()));
};
let entry = wm_memory::attestation::RecordAttestation {
domain: wm_memory::attestation::ATTESTATION_DOMAIN.to_string(),
galaxy: galaxy.db_name().to_string(),
memory_id: id.to_string(),
record_hash: record_hash.to_string(),
agent_id,
timestamp,
public_key_hex,
signature_hex,
};
if let Err(e) = store.record_attestation(galaxy, id, &entry) {
tracing::warn!("creation attestation write failed for memory {id}: {e}");
return (false, Some("attestation store write failed".to_string()));
}
(true, None)
}
pub struct MemoryCreateTool {
store: Arc<MemoryStore>,
search: Option<Arc<SearchEngine>>,
recall: Option<Arc<RecallEngine>>,
stats: ToolStats,
effects: EffectRow,
attestation_key: Option<String>,
}
impl MemoryCreateTool {
pub fn new(
store: Arc<MemoryStore>,
search: Option<Arc<SearchEngine>>,
recall: Option<Arc<RecallEngine>>,
) -> Self {
Self {
store,
search,
recall,
stats: ToolStats::default(),
effects: EffectRow {
writes: fresh_write_galaxies(),
invokes: vec![Capability::MemoryWrite],
sandbox: wm_core::Sandbox::StoreScoped,
..Default::default()
},
attestation_key: node_attestation_key(),
}
}
#[must_use]
pub fn with_attestation_key(
store: Arc<MemoryStore>,
search: Option<Arc<SearchEngine>>,
recall: Option<Arc<RecallEngine>>,
attestation_key: Option<String>,
) -> Self {
let mut tool = Self::new(store, search, recall);
tool.attestation_key = attestation_key;
tool
}
}
#[async_trait]
impl Tool for MemoryCreateTool {
fn name(&self) -> &str {
"memory.create"
}
fn gana(&self) -> Gana {
Gana::Encampment
}
fn effects(&self) -> &EffectRow {
&self.effects
}
fn input_schema(&self) -> Value {
schema(
&json!({
"content": str_prop("Memory content (text)"),
"galaxy": str_prop("Target galaxy (default codex)"),
"tags": str_array_prop("Optional tags"),
"title": str_prop("Optional human-readable title (envelope v2)"),
"topic": str_prop("Optional topic label for subject-scoped retrieval (envelope v2)"),
"importance": num_prop("Optional importance 0.0-1.0 (write gate applies class ceilings/floors when the class is recognized)"),
"source": str_prop("Authorship claim: user (user-dictated content, trust 1.0) | agent (default, trust 0.7) | other free-form class (trust 0.7)"),
}),
&["content"],
)
}
async fn call(&self, ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
let content = args
.get("content")
.and_then(|v| v.as_str())
.ok_or_else(|| wm_core::CoreError::InvalidArgs("content (string) required".into()))?;
content_admission_gate(content).map_err(wm_core::CoreError::InvalidArgs)?;
let galaxy_str = args
.get("galaxy")
.and_then(|v| v.as_str())
.unwrap_or("codex");
let galaxy = parse_galaxy(galaxy_str)?;
let tags: Vec<String> = args
.get("tags")
.and_then(|v| v.as_array())
.map(|a| {
a.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default();
if let Some(search) = &self.search {
if search.is_readonly() {
return Err(wm_core::CoreError::InvalidArgs(
"read-only mode: memory.create disabled (another process owns the index)"
.into(),
));
}
}
let kinds = wm_memory::credential_shaped_content(content);
let warnings: Vec<String> = kinds
.iter()
.map(|k| {
format!(
"content looks like a credential ({k}) — {}",
wm_memory::CREDENTIAL_ADVICE
)
})
.collect();
let mut memory = Memory::new(galaxy, content.to_string());
memory.metadata.tags = tags;
memory.metadata.title = args
.get("title")
.and_then(Value::as_str)
.map(str::trim)
.filter(|s| !s.is_empty())
.map(String::from);
memory.metadata.topic = args
.get("topic")
.and_then(Value::as_str)
.map(str::trim)
.filter(|s| !s.is_empty())
.map(String::from);
if let Some(importance) =
wm_dispatch::write_gate::parse_importance_value(args.get("importance"))
.map_err(wm_core::CoreError::InvalidArgs)?
{
memory.metadata.importance = importance;
}
memory.metadata.class = wm_memory::typology::detect_class(content, &memory.metadata.tags);
memory.metadata.tier = memory.metadata.class.map_or(
wm_memory::memory::Tier::Working,
wm_memory::typology::initial_tier,
);
let claimed_source = args
.get("source")
.and_then(Value::as_str)
.map(str::trim)
.filter(|s| !s.is_empty());
let (source, trust) = match claimed_source {
Some("user") => ("user", 1.0),
Some(other) => (other, 0.7),
None => ("agent", 0.7),
};
memory.metadata.source = source.to_string();
memory.metadata.source_trust = trust;
let id = memory.metadata.id;
if let Some(recall) = &self.recall {
if let Err(e) = recall.store_with_embedding(galaxy, &memory) {
tracing::warn!("RecallEngine store_with_embedding failed for memory {id}: {e}");
self.store.put(galaxy, &memory)?;
if let Some(search) = &self.search {
if let Err(e) = (|| {
let mut writer = search.writer()?;
search.add_document(
&mut writer,
&id.to_string(),
galaxy.db_name(),
content,
&memory.metadata.tags,
memory.metadata.created_at.timestamp(),
)?;
search.commit(&mut writer)?;
Ok::<(), wm_core::CoreError>(())
})() {
tracing::warn!("Tantivy indexing failed for memory {id}: {e}");
}
}
}
} else {
self.store.put(galaxy, &memory)?;
if let Some(search) = &self.search {
if let Err(e) = (|| {
let mut writer = search.writer()?;
search.add_document(
&mut writer,
&id.to_string(),
galaxy.db_name(),
content,
&memory.metadata.tags,
memory.metadata.created_at.timestamp(),
)?;
search.commit(&mut writer)?;
Ok::<(), wm_core::CoreError>(())
})() {
tracing::warn!("Tantivy indexing failed for memory {id}: {e}");
}
}
}
let episodic_capture_error = capture_explicit_memory(
&self.store,
&memory,
EpisodicKind::Observation,
if source == "user" {
ProvenanceSource::User
} else {
ProvenanceSource::Agent
},
ctx.session_id,
0,
);
let (attested, attested_reason) = attest_created_memory(
&self.store,
galaxy,
id,
&memory.metadata.content_hash,
ctx,
self.attestation_key.as_deref(),
);
let mut response = json!({
"status": "success",
"id": id.to_string(),
"galaxy": galaxy.db_name(),
"content_hash": memory.metadata.content_hash,
"source": source,
"source_trust": trust,
"attested": attested,
});
if let Some(reason) = attested_reason {
response["attested_reason"] = json!(reason);
}
if !warnings.is_empty() {
response["warnings"] = json!(warnings);
}
attach_episodic_capture_warning(&mut response, episodic_capture_error);
Ok(response)
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct MemoryBatchCreateTool {
store: Arc<MemoryStore>,
search: Option<Arc<SearchEngine>>,
recall: Option<Arc<RecallEngine>>,
stats: ToolStats,
effects: EffectRow,
attestation_key: Option<String>,
}
impl MemoryBatchCreateTool {
pub fn new(
store: Arc<MemoryStore>,
search: Option<Arc<SearchEngine>>,
recall: Option<Arc<RecallEngine>>,
) -> Self {
Self {
store,
search,
recall,
stats: ToolStats::default(),
effects: EffectRow {
writes: fresh_write_galaxies(),
invokes: vec![Capability::MemoryWrite],
sandbox: wm_core::Sandbox::StoreScoped,
..Default::default()
},
attestation_key: node_attestation_key(),
}
}
#[must_use]
pub fn with_attestation_key(
store: Arc<MemoryStore>,
search: Option<Arc<SearchEngine>>,
recall: Option<Arc<RecallEngine>>,
attestation_key: Option<String>,
) -> Self {
let mut tool = Self::new(store, search, recall);
tool.attestation_key = attestation_key;
tool
}
}
#[async_trait]
impl Tool for MemoryBatchCreateTool {
fn name(&self) -> &str {
"memory.batch_create"
}
fn gana(&self) -> Gana {
Gana::Encampment
}
fn effects(&self) -> &EffectRow {
&self.effects
}
fn input_schema(&self) -> Value {
schema(
&json!({
"items": {
"type": "array",
"description": "Array of {content, galaxy?, tags?} objects",
"items": {
"type": "object",
"properties": {
"content": str_prop("Memory content (text)"),
"galaxy": str_prop("Target galaxy (default codex)"),
"tags": str_array_prop("Optional tags"),
"importance": num_prop("Optional importance 0.0-1.0 (write gate applies class ceilings/floors when the class is recognized)"),
},
"required": ["content"],
},
},
}),
&["items"],
)
}
async fn call(&self, ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
let items = args
.get("items")
.and_then(|v| v.as_array())
.ok_or_else(|| wm_core::CoreError::InvalidArgs("items (array) required".into()))?;
if let Some(search) = &self.search {
if search.is_readonly() {
return Err(wm_core::CoreError::InvalidArgs(
"read-only mode: memory.batch_create disabled (another process owns the index)"
.into(),
));
}
}
let mut ids: Vec<String> = Vec::new();
let mut all_items_user_claimed = true;
let mut cred_kinds: Vec<&'static str> = Vec::new();
let mut writer_guard = if self.recall.is_none() {
if let Some(search) = &self.search {
Some(search.writer()?)
} else {
None
}
} else {
None
};
let mut memories: Vec<(Galaxy, Memory)> = Vec::new();
for (index, item) in items.iter().enumerate() {
let content = item
.get("content")
.and_then(|v| v.as_str())
.ok_or_else(|| {
wm_core::CoreError::InvalidArgs("each item needs content (string)".into())
})?;
content_admission_gate(content).map_err(|reason| {
wm_core::CoreError::InvalidArgs(format!("items[{index}]: {reason}"))
})?;
let galaxy_str = item
.get("galaxy")
.and_then(|v| v.as_str())
.unwrap_or("codex");
let galaxy = parse_galaxy(galaxy_str)?;
let tags: Vec<String> = item
.get("tags")
.and_then(|v| v.as_array())
.map(|a| {
a.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default();
let mut memory = Memory::new(galaxy, content.to_string());
memory.metadata.tags = tags;
if let Some(importance) =
wm_dispatch::write_gate::parse_importance_value(item.get("importance"))
.map_err(wm_core::CoreError::InvalidArgs)?
{
memory.metadata.importance = importance;
}
memory.metadata.class =
wm_memory::typology::detect_class(content, &memory.metadata.tags);
memory.metadata.tier = memory.metadata.class.map_or(
wm_memory::memory::Tier::Working,
wm_memory::typology::initial_tier,
);
let claimed_source = item
.get("source")
.and_then(Value::as_str)
.map(str::trim)
.filter(|s| !s.is_empty());
let (source, trust) = match claimed_source {
Some("user") => ("user", 1.0),
Some(other) => (other, 0.7),
None => ("agent", 0.7),
};
if source != "user" {
all_items_user_claimed = false;
}
memory.metadata.source = source.to_string();
memory.metadata.source_trust = trust;
let id = memory.metadata.id;
ids.push(id.to_string());
for k in wm_memory::credential_shaped_content(content) {
if !cred_kinds.contains(&k) {
cred_kinds.push(k);
}
}
memories.push((galaxy, memory));
}
if let Some(recall) = &self.recall {
let entries: Vec<(Galaxy, &Memory)> = memories.iter().map(|(g, m)| (*g, m)).collect();
match recall.store_batch_with_embedding(&entries) {
Ok(n) => {
tracing::info!("batch_create: embedded {n} memories in single batch");
}
Err(e) => {
tracing::warn!(
"batch_create: store_batch_with_embedding failed ({e}), falling back to per-item"
);
let mut fallback_writer = if writer_guard.is_none() {
if let Some(search) = &self.search {
search.writer().ok()
} else {
None
}
} else {
None
};
for (galaxy, memory) in &memories {
self.store.put(*galaxy, memory)?;
let writer_slot = writer_guard.as_mut().or(fallback_writer.as_mut());
if let Some(guard) = writer_slot {
if let Some(search) = &self.search {
if let Err(e) = search.add_document(
guard,
&memory.metadata.id.to_string(),
galaxy.db_name(),
&memory.content,
&memory.metadata.tags,
memory.metadata.created_at.timestamp(),
) {
tracing::warn!(
"Tantivy indexing failed for memory {}: {e}",
memory.metadata.id
);
}
}
}
}
if let Some(mut guard) = fallback_writer {
if let Some(search) = &self.search {
if let Err(e) = search.commit(&mut guard) {
tracing::warn!("Tantivy fallback commit failed: {e}");
}
}
}
}
}
} else {
for (galaxy, memory) in &memories {
self.store.put(*galaxy, memory)?;
if let Some(ref mut guard) = writer_guard {
if let Some(search) = &self.search {
if let Err(e) = search.add_document(
&mut *guard,
&memory.metadata.id.to_string(),
galaxy.db_name(),
&memory.content,
&memory.metadata.tags,
memory.metadata.created_at.timestamp(),
) {
tracing::warn!(
"Tantivy indexing failed for memory {}: {e}",
memory.metadata.id
);
}
}
}
}
}
if let Some(ref mut guard) = writer_guard {
if let Some(search) = &self.search {
if let Err(e) = search.commit(&mut *guard) {
tracing::warn!("Tantivy batch commit failed: {e}");
}
}
}
let episodic_capture_error = capture_explicit_memories(
&self.store,
&memories,
EpisodicKind::Observation,
if all_items_user_claimed {
ProvenanceSource::User
} else {
ProvenanceSource::Agent
},
ctx.session_id,
);
let mut attested_count = 0usize;
for (galaxy, memory) in &memories {
let (ok, _) = attest_created_memory(
&self.store,
*galaxy,
memory.metadata.id,
&memory.metadata.content_hash,
ctx,
self.attestation_key.as_deref(),
);
attested_count += usize::from(ok);
}
let mut response = json!({
"status": "success",
"count": ids.len(),
"ids": ids,
"attested_count": attested_count,
});
let warnings: Vec<String> = cred_kinds
.iter()
.map(|k| {
format!(
"some items look like credentials ({k}) — {}",
wm_memory::CREDENTIAL_ADVICE
)
})
.collect();
if !warnings.is_empty() {
response["warnings"] = json!(warnings);
}
attach_episodic_capture_warning(&mut response, episodic_capture_error);
Ok(response)
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct MemoryReadTool {
store: Arc<MemoryStore>,
stats: ToolStats,
effects: EffectRow,
}
impl MemoryReadTool {
pub fn new(store: Arc<MemoryStore>) -> Self {
Self {
store,
stats: ToolStats::default(),
effects: EffectRow::read_only(vec![Resource::Galaxy("codex".into())]),
}
}
}
#[async_trait]
impl Tool for MemoryReadTool {
fn name(&self) -> &str {
"memory.read"
}
fn gana(&self) -> Gana {
Gana::WinnowingBasket
}
fn effects(&self) -> &EffectRow {
&self.effects
}
fn input_schema(&self) -> Value {
schema(
&json!({
"id": str_prop("Memory UUID"),
"galaxy": str_prop("Galaxy containing the memory (default codex)"),
}),
&["id"],
)
}
async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
let id_str = args
.get("id")
.and_then(|v| v.as_str())
.ok_or_else(|| wm_core::CoreError::InvalidArgs("id (string) required".into()))?;
let id = uuid::Uuid::parse_str(id_str)
.map_err(|e| wm_core::CoreError::InvalidArgs(format!("invalid UUID: {e}")))?;
let galaxy_str = args
.get("galaxy")
.and_then(|v| v.as_str())
.unwrap_or("codex");
let galaxy = parse_galaxy(galaxy_str)?;
let memory = if let Some(memory) = self.store.get(galaxy, id)? {
memory
} else {
let Some(record) = self.store.get_cold_record(id)? else {
return Ok(json!({
"status": "not_found",
"id": id_str,
"galaxy": galaxy.db_name(),
}));
};
if record.id != id || record.galaxy != galaxy {
return Ok(json!({
"status": "not_found",
"id": id_str,
"galaxy": galaxy.db_name(),
}));
}
let memory = record.decompress()?;
if memory.metadata.id != id
|| memory.metadata.galaxy != galaxy
|| memory.metadata.content_hash != record.content_hash
|| wm_memory::content_hash(&memory.content) != record.content_hash
{
return Err(wm_core::CoreError::Memory(
"cold memory header/payload integrity mismatch".into(),
));
}
memory
};
if memory.metadata.is_private {
return Ok(json!({
"status": "not_found",
"id": id_str,
"galaxy": galaxy.db_name(),
}));
}
Ok(json!({
"status": "success",
"id": memory.metadata.id.to_string(),
"galaxy": memory.metadata.galaxy.db_name(),
"content": memory.content,
"tags": memory.metadata.tags,
"created_at": memory.metadata.created_at.to_rfc3339(),
}))
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct MemoryListTool {
store: Arc<MemoryStore>,
stats: ToolStats,
effects: EffectRow,
}
impl MemoryListTool {
pub fn new(store: Arc<MemoryStore>) -> Self {
Self {
store,
stats: ToolStats::default(),
effects: EffectRow::read_only(vec![Resource::Galaxy("codex".into())]),
}
}
}
#[async_trait]
impl Tool for MemoryListTool {
fn name(&self) -> &str {
"memory.list"
}
fn gana(&self) -> Gana {
Gana::WinnowingBasket
}
fn effects(&self) -> &EffectRow {
&self.effects
}
fn input_schema(&self) -> Value {
schema(
&json!({
"galaxy": str_prop("Galaxy to list (default codex)"),
"limit": int_prop("Maximum entries (default 20)"),
"offset": int_prop("Skip this many matching entries before returning (default 0)"),
"exclude_tags": {
"type": "array",
"items": {"type": "string"},
"description": "Drop memories carrying any of these tags",
},
}),
&[],
)
}
async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
let galaxy_str = args
.get("galaxy")
.and_then(|v| v.as_str())
.unwrap_or("codex");
let limit = args
.get("limit")
.and_then(serde_json::Value::as_u64)
.unwrap_or(20) as usize;
let offset = args
.get("offset")
.and_then(serde_json::Value::as_u64)
.unwrap_or(0) as usize;
let exclude_tags: Vec<String> = args
.get("exclude_tags")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|t| t.as_str().map(String::from))
.collect()
})
.unwrap_or_default();
let galaxy = parse_galaxy(galaxy_str)?;
let memories = self.store.scan(galaxy, 10_000)?;
let total = self.store.count(galaxy)?;
let visible: Vec<&wm_memory::Memory> = memories
.iter()
.filter(|m| crate::expansion::common::mcp_visible(m))
.filter(|m| crate::expansion::common::validity_visible(m))
.filter(|m| {
!exclude_tags
.iter()
.any(|t| m.metadata.tags.iter().any(|mt| mt == t))
})
.collect();
let entries: Vec<Value> = visible
.iter()
.skip(offset)
.take(limit)
.map(|m| {
json!({
"id": m.metadata.id.to_string(),
"content_preview": m.content.chars().take(80).collect::<String>(),
"tags": m.metadata.tags,
"created_at": m.metadata.created_at.to_rfc3339(),
})
})
.collect();
Ok(json!({
"status": "success",
"galaxy": galaxy.db_name(),
"total": total,
"matched": visible.len(),
"offset": offset,
"returned": entries.len(),
"memories": entries,
}))
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct GnosisTool {
store: Arc<MemoryStore>,
tool_count: usize,
stats: ToolStats,
effects: EffectRow,
}
impl GnosisTool {
pub fn new(store: Arc<MemoryStore>) -> Self {
Self {
store,
tool_count: 0,
stats: ToolStats::default(),
effects: EffectRow::read_only(vec![Resource::Galaxy("substrate".into())]),
}
}
pub fn with_tool_count(store: Arc<MemoryStore>, tool_count: usize) -> Self {
Self {
store,
tool_count,
stats: ToolStats::default(),
effects: EffectRow::read_only(vec![Resource::Galaxy("substrate".into())]),
}
}
}
#[async_trait]
impl Tool for GnosisTool {
fn input_schema(&self) -> Value {
schema(&json!({}), &[])
}
fn name(&self) -> &str {
"gnosis"
}
fn gana(&self) -> Gana {
Gana::Root
}
fn effects(&self) -> &EffectRow {
&self.effects
}
async fn call(&self, ctx: &mut Context, _args: Value) -> wm_core::Result<Value> {
let mut galaxy_stats = serde_json::Map::new();
for galaxy in Galaxy::all() {
let count = self.store.count(galaxy).unwrap_or(0);
if count > 0 {
galaxy_stats.insert(galaxy.db_name().to_string(), json!(count));
}
}
Ok(json!({
"status": "success",
"version": env!("CARGO_PKG_VERSION"),
"store_path": self.store.path().display().to_string(),
"brain_wave": format!("{:?}", ctx.brain_wave),
"available_tools": self.tool_count,
"galaxies_with_data": galaxy_stats.len(),
"galaxy_counts": galaxy_stats,
"ganas": Gana::COUNT,
"galaxies": Galaxy::COUNT,
}))
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct ToolsListTool {
registry: Arc<ToolRegistry>,
stats: ToolStats,
effects: EffectRow,
}
impl ToolsListTool {
#[must_use]
pub fn new(registry: Arc<ToolRegistry>) -> Self {
Self {
registry,
stats: ToolStats::default(),
effects: EffectRow::pure(),
}
}
}
#[async_trait]
impl Tool for ToolsListTool {
fn input_schema(&self) -> Value {
schema(&json!({}), &[])
}
fn name(&self) -> &str {
"tools.list"
}
fn gana(&self) -> Gana {
Gana::Ghost
}
fn effects(&self) -> &EffectRow {
&self.effects
}
async fn call(&self, ctx: &mut Context, _args: Value) -> wm_core::Result<Value> {
let available = self.registry.available_in(ctx.brain_wave);
let tools: Vec<Value> = available
.iter()
.map(|t| {
let effects = t.effects();
json!({
"name": t.name(),
"gana": format!("{:?}", t.gana()),
"description": t.description(),
"input_schema": t.input_schema(),
"annotations": {
"readOnlyHint": effects.writes.is_empty(),
"destructiveHint": effects.destructive,
},
})
})
.collect();
Ok(json!({
"status": "success",
"brain_wave": format!("{:?}", ctx.brain_wave),
"total": tools.len(),
"tools": tools,
}))
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct MemoryDeleteTool {
store: Arc<MemoryStore>,
search: Option<Arc<SearchEngine>>,
stats: ToolStats,
effects: EffectRow,
}
impl MemoryDeleteTool {
pub fn new(store: Arc<MemoryStore>, search: Option<Arc<SearchEngine>>) -> Self {
Self {
store,
search,
stats: ToolStats::default(),
effects: EffectRow {
writes: memory_galaxy_writes(),
reads: memory_galaxy_reads(),
invokes: vec![Capability::MemoryWrite],
destructive: true,
sandbox: wm_core::Sandbox::StoreScoped,
..Default::default()
},
}
}
}
#[async_trait]
impl Tool for MemoryDeleteTool {
fn name(&self) -> &str {
"memory.delete"
}
fn gana(&self) -> Gana {
Gana::Encampment
}
fn effects(&self) -> &EffectRow {
&self.effects
}
fn input_schema(&self) -> Value {
schema(
&json!({
"id": str_prop("Memory UUID"),
"galaxy": str_prop("Galaxy containing the memory (optional; when omitted the id is resolved across all memory galaxies)"),
"confirm": bool_prop("Required — memory.delete is destructive"),
}),
&["id", "confirm"],
)
}
async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
let id_str = args
.get("id")
.and_then(|v| v.as_str())
.ok_or_else(|| wm_core::CoreError::InvalidArgs("id (string) required".into()))?;
let id = uuid::Uuid::parse_str(id_str)
.map_err(|e| wm_core::CoreError::InvalidArgs(format!("invalid UUID: {e}")))?;
if let Some(search) = &self.search {
if search.is_readonly() {
return Err(wm_core::CoreError::InvalidArgs(
"read-only mode: memory.delete disabled (another process owns the index)"
.into(),
));
}
}
let targets: Vec<Galaxy> = match args.get("galaxy").and_then(|v| v.as_str()) {
Some(g) => vec![parse_galaxy(g)?],
None => Galaxy::memory_galaxies().to_vec(),
};
let mut deleted_from: Vec<&str> = Vec::new();
for galaxy in targets {
if self.store.delete(galaxy, id)? {
deleted_from.push(galaxy.db_name());
}
}
if !deleted_from.is_empty() {
if let Some(search) = &self.search {
if let Err(e) = (|| {
let mut writer = search.writer()?;
search.delete_document(&mut writer, id_str)?;
search.commit(&mut writer)?;
Ok::<(), wm_core::CoreError>(())
})() {
tracing::warn!("Tantivy de-indexing failed for memory {id_str}: {e}");
}
}
}
if deleted_from.is_empty() {
return Ok(json!({
"status": "not_found",
"id": id_str,
"hint": "id not found in any memory galaxy; pass an explicit galaxy to target one"
}));
}
let mut body = serde_json::Map::new();
body.insert("status".into(), json!("success"));
body.insert("id".into(), json!(id_str));
if args.get("galaxy").and_then(|v| v.as_str()).is_some() {
body.insert("galaxy".into(), json!(deleted_from[0]));
}
body.insert(
"galaxies".into(),
json!(deleted_from.iter().map(|g| json!(g)).collect::<Vec<_>>()),
);
body.insert("deleted".into(), json!(deleted_from.len()));
Ok(Value::Object(body))
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct MemoryBatchDeleteTool {
store: Arc<MemoryStore>,
search: Option<Arc<SearchEngine>>,
stats: ToolStats,
effects: EffectRow,
}
impl MemoryBatchDeleteTool {
pub fn new(store: Arc<MemoryStore>, search: Option<Arc<SearchEngine>>) -> Self {
Self {
store,
search,
stats: ToolStats::default(),
effects: EffectRow {
writes: memory_galaxy_writes(),
reads: memory_galaxy_reads(),
invokes: vec![Capability::MemoryWrite],
destructive: true,
..Default::default()
},
}
}
}
#[async_trait]
impl Tool for MemoryBatchDeleteTool {
fn name(&self) -> &str {
"memory.batch_delete"
}
fn gana(&self) -> Gana {
Gana::Encampment
}
fn effects(&self) -> &EffectRow {
&self.effects
}
fn input_schema(&self) -> Value {
schema(
&json!({
"ids": {"type": "array", "items": {"type": "string"},
"description": "Memory UUIDs to delete (max 200000)"},
"confirm": bool_prop("Required — memory.batch_delete is destructive"),
}),
&["ids", "confirm"],
)
}
async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
const MAX_IDS: usize = 200_000;
if !args
.get("confirm")
.and_then(serde_json::Value::as_bool)
.unwrap_or(false)
{
return Err(wm_core::CoreError::InvalidArgs(
"confirm (bool) required — memory.batch_delete is destructive".into(),
));
}
let ids: Vec<String> = args
.get("ids")
.and_then(|v| v.as_array())
.map(|a| {
a.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.ok_or_else(|| {
wm_core::CoreError::InvalidArgs("ids (array of UUID strings) required".into())
})?;
if ids.is_empty() {
return Ok(json!({"status": "success", "requested": 0, "deleted": 0, "not_found": 0}));
}
if ids.len() > MAX_IDS {
return Err(wm_core::CoreError::InvalidArgs(format!(
"ids capped at {MAX_IDS}; split the batch"
)));
}
if let Some(search) = &self.search {
if search.is_readonly() {
return Err(wm_core::CoreError::InvalidArgs(
"read-only mode: memory.batch_delete disabled (another process owns the index)"
.into(),
));
}
}
let targets: Vec<Galaxy> = Galaxy::memory_galaxies().to_vec();
let mut deleted_ids: Vec<(String, Vec<&str>)> = Vec::new();
let mut not_found: usize = 0;
for id_str in &ids {
let Ok(id) = uuid::Uuid::parse_str(id_str) else {
not_found += 1;
continue;
};
let mut deleted_from: Vec<&str> = Vec::new();
for galaxy in targets.iter().copied() {
if self.store.delete(galaxy, id)? {
deleted_from.push(galaxy.db_name());
}
}
if deleted_from.is_empty() {
not_found += 1;
} else {
deleted_ids.push((id_str.clone(), deleted_from));
}
}
if !deleted_ids.is_empty() {
if let Some(search) = &self.search {
if let Err(e) = (|| {
let mut writer = search.writer()?;
for (id_str, _) in &deleted_ids {
search.delete_document(&mut writer, id_str)?;
}
search.commit(&mut writer)?;
Ok::<(), wm_core::CoreError>(())
})() {
tracing::warn!(
"Tantivy batch de-indexing failed ({} ids): {e}",
deleted_ids.len()
);
}
}
}
Ok(json!({
"status": "success",
"requested": ids.len(),
"deleted": deleted_ids.len(),
"not_found": not_found,
}))
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct MemoryQueryTool {
store: Arc<MemoryStore>,
stats: ToolStats,
effects: EffectRow,
}
impl MemoryQueryTool {
pub fn new(store: Arc<MemoryStore>) -> Self {
Self {
store,
stats: ToolStats::default(),
effects: EffectRow::read_only(vec![Resource::Galaxy("codex".into())]),
}
}
}
#[async_trait]
impl Tool for MemoryQueryTool {
fn name(&self) -> &str {
"memory.query"
}
fn gana(&self) -> Gana {
Gana::WinnowingBasket
}
fn effects(&self) -> &EffectRow {
&self.effects
}
fn input_schema(&self) -> Value {
schema(
&json!({
"query": str_prop("Case-insensitive substring filter over content (literal match). For tokenized, ranked full-text retrieval use memory.search"),
"galaxy": str_prop("Galaxy to query (default codex)"),
"tags": str_array_prop("Filter: memories with all of these tags"),
"min_importance": num_prop("Filter: minimum importance (0-1)"),
"max_importance": num_prop("Filter: maximum importance (0-1)"),
"created_after": str_prop("Filter: only memories created at or after this RFC 3339 timestamp (e.g. 2026-08-01T00:00:00Z)"),
"created_before": str_prop("Filter: only memories created at or before this RFC 3339 timestamp"),
"limit": int_prop("Maximum entries (default 50)"),
}),
&[],
)
}
async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
let galaxy_str = args
.get("galaxy")
.and_then(|v| v.as_str())
.unwrap_or("codex");
let galaxy = parse_galaxy(galaxy_str)?;
let limit = args
.get("limit")
.and_then(serde_json::Value::as_u64)
.unwrap_or(50) as usize;
let mut query = MemoryQuery::new().with_limit(limit);
if let Some(text) = args
.get("query")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|s| !s.is_empty())
{
query = query.with_content_substring(text);
}
if let Some(tags) = args.get("tags").and_then(|v| v.as_array()) {
let tag_list: Vec<String> = tags
.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect();
if !tag_list.is_empty() {
query = query.with_tags(tag_list);
}
}
let parse_bound = |name: &str| -> wm_core::Result<Option<chrono::DateTime<chrono::Utc>>> {
match args.get(name).and_then(|v| v.as_str()) {
Some(s) if !s.trim().is_empty() => chrono::DateTime::parse_from_rfc3339(s.trim())
.map(|t| Some(t.with_timezone(&chrono::Utc)))
.map_err(|_| {
wm_core::CoreError::InvalidArgs(format!(
"{name} must be an RFC 3339 timestamp (e.g. \"2026-08-01T00:00:00Z\"), got: {s}"
))
}),
_ => Ok(None),
}
};
let created_after = parse_bound("created_after")?;
let created_before = parse_bound("created_before")?;
if let Some(after) = created_after {
query = query.with_created_after(after);
}
if let Some(before) = created_before {
query = query.with_created_before(before);
}
let min_imp = args
.get("min_importance")
.and_then(serde_json::Value::as_f64);
let max_imp = args
.get("max_importance")
.and_then(serde_json::Value::as_f64);
if let (Some(min), Some(max)) = (min_imp, max_imp) {
query = query.with_importance_range(min as f32, max as f32);
} else if let Some(min) = min_imp {
query = query.with_importance_range(min as f32, 1.0);
}
let memories = self.store.query(galaxy, &query)?;
let entries: Vec<Value> = memories
.iter()
.filter(|m| crate::expansion::common::mcp_visible(m))
.filter(|m| crate::expansion::common::validity_visible(m))
.map(|m| {
json!({
"id": m.metadata.id.to_string(),
"content_preview": m.content.chars().take(80).collect::<String>(),
"tags": m.metadata.tags,
"importance": m.metadata.importance,
"created_at": m.metadata.created_at.to_rfc3339(),
})
})
.collect();
let query_applied = args
.get("query")
.and_then(|v| v.as_str())
.is_some_and(|s| !s.trim().is_empty());
let mut response = json!({
"status": "success",
"galaxy": galaxy.db_name(),
"total": entries.len(),
"memories": entries,
});
if query_applied {
response["note"] = json!(
"'query' applied as a literal substring filter over content — \
for tokenized, ranked full-text retrieval use memory.search."
);
}
if created_after.is_some() || created_before.is_some() {
response["time_range"] = json!({
"created_after": created_after
.map(|t| t.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)),
"created_before": created_before
.map(|t| t.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)),
});
}
Ok(response)
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
#[allow(dead_code)]
pub struct MemorySearchTool {
search: Arc<SearchEngine>,
store: Arc<MemoryStore>,
stats: ToolStats,
effects: EffectRow,
}
impl MemorySearchTool {
#[must_use]
pub fn new(search: Arc<SearchEngine>, store: Arc<MemoryStore>) -> Self {
Self {
search,
store,
stats: ToolStats::default(),
effects: EffectRow::read_only(vec![Resource::Galaxy("codex".into())]),
}
}
}
#[async_trait]
impl Tool for MemorySearchTool {
fn name(&self) -> &str {
"memory.search"
}
fn gana(&self) -> Gana {
Gana::WinnowingBasket
}
fn effects(&self) -> &EffectRow {
&self.effects
}
fn input_schema(&self) -> Value {
schema(
&json!({
"query": str_prop("Full-text query"),
"galaxy": str_prop("Galaxy filter (default: all galaxies)"),
"limit": int_prop("Maximum results (default 20)"),
"min_score": num_prop("Absolute BM25 score floor"),
"min_score_ratio": num_prop("Relative floor: reject hits below this fraction of the top score"),
}),
&["query"],
)
}
async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
let query = args
.get("query")
.and_then(|v| v.as_str())
.ok_or_else(|| wm_core::CoreError::InvalidArgs("query (string) required".into()))?;
let limit = args
.get("limit")
.and_then(serde_json::Value::as_u64)
.unwrap_or(20) as usize;
let min_score = args
.get("min_score")
.and_then(serde_json::Value::as_f64)
.map(|v| v as f32)
.filter(|v| *v > 0.0);
let min_score_ratio = args
.get("min_score_ratio")
.and_then(serde_json::Value::as_f64)
.map(|v| v as f32)
.filter(|v| *v > 0.0 && *v < 1.0);
let galaxy_str = args.get("galaxy").and_then(|v| v.as_str());
let mut opts = wm_memory::SearchOptions {
limit,
min_score,
relative_floor: min_score_ratio,
..wm_memory::SearchOptions::default()
};
if let Some(g) = galaxy_str {
opts.galaxy = Some(parse_galaxy(g)?);
}
let results = self.search.search_opt(query, &opts)?;
let entries: Vec<Value> = results
.iter()
.filter_map(|r| {
let galaxy = wm_core::Galaxy::from_db_name(&r.galaxy)?;
let id = uuid::Uuid::parse_str(&r.memory_id).ok()?;
let mem = self.store.get(galaxy, id).ok().flatten()?;
if !crate::expansion::common::mcp_visible(&mem) {
return None;
}
if !crate::expansion::common::validity_visible(&mem) {
return None;
}
Some(json!({
"memory_id": r.memory_id,
"galaxy": r.galaxy,
"score": r.score,
"normalized_score": r.normalized_score,
"content_preview": wm_memory::scrub_text(&mem.content).chars().take(120).collect::<String>(),
}))
})
.collect();
Ok(json!({
"status": "success",
"query": query,
"total": entries.len(),
"results": entries,
}))
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct MemoryChatTool {
search: std::sync::Mutex<ConversationalSearch>,
stats: ToolStats,
effects: EffectRow,
}
impl MemoryChatTool {
#[must_use]
pub fn new(search: ConversationalSearch) -> Self {
Self {
search: std::sync::Mutex::new(search),
stats: ToolStats::default(),
effects: EffectRow::read_only(vec![Resource::Galaxy("codex".into())]),
}
}
}
#[async_trait]
impl Tool for MemoryChatTool {
fn name(&self) -> &str {
"memory.chat"
}
fn gana(&self) -> Gana {
Gana::WinnowingBasket
}
fn effects(&self) -> &EffectRow {
&self.effects
}
fn input_schema(&self) -> Value {
schema(
&json!({
"query": str_prop("Conversational query"),
"galaxy": str_prop("Optional galaxy filter"),
"limit": int_prop("Maximum results"),
}),
&["query"],
)
}
async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
let query = args
.get("query")
.and_then(|v| v.as_str())
.ok_or_else(|| wm_core::CoreError::InvalidArgs("query (string) required".into()))?;
let limit = args
.get("limit")
.and_then(serde_json::Value::as_u64)
.map(|n| n as usize);
let galaxy_str = args.get("galaxy").and_then(|v| v.as_str());
let galaxy = match galaxy_str {
Some(g) => Some(parse_galaxy(g)?),
None => None,
};
let (results, metrics) = {
let search = self
.search
.lock()
.map_err(|e| wm_core::CoreError::Tool(format!("search lock: {e}")))?;
let results = search.search_in_galaxy(query, limit, galaxy);
let metrics = search.metrics();
(results, metrics)
};
let entries: Vec<Value> = results
.iter()
.map(|r| {
json!({
"memory_id": r.memory_id,
"galaxy": format!("{:?}", r.galaxy),
"score": r.score,
"snippet": r.snippet,
"from_cache": r.from_cache,
"latency_us": r.latency_us,
})
})
.collect();
Ok(json!({
"status": "success",
"query": query,
"total": entries.len(),
"results": entries,
"metrics": {
"total_queries": metrics.total_queries,
"cache_hits": metrics.cache_hits,
"cache_misses": metrics.cache_misses,
"cache_hit_rate": metrics.cache_hit_rate(),
"avg_latency_ms": metrics.avg_latency_ms(),
"meets_latency_target": metrics.meets_latency_target(),
},
}))
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct MemoryVectorSearchTool {
store: Arc<MemoryStore>,
vector_store: Arc<std::sync::Mutex<VectorStore>>,
stats: ToolStats,
effects: EffectRow,
}
impl MemoryVectorSearchTool {
#[must_use]
pub fn new(store: Arc<MemoryStore>, vector_store: Arc<std::sync::Mutex<VectorStore>>) -> Self {
Self {
store,
vector_store,
stats: ToolStats::default(),
effects: EffectRow::read_only(vec![Resource::VectorStore]),
}
}
fn ensure_loaded(&self) -> wm_core::Result<()> {
let mut vs = self
.vector_store
.lock()
.map_err(|e| wm_core::CoreError::Tool(format!("vector store lock: {e}")))?;
if !vs.is_loaded() {
vs.load(&self.store)?;
}
drop(vs);
Ok(())
}
}
#[async_trait]
impl Tool for MemoryVectorSearchTool {
fn input_schema(&self) -> Value {
schema(
&json!({
"memory_id": str_prop("Memory UUID whose stored embedding is the query"),
"embedding": json!({"type": "array", "items": {"type": "number"}, "description": "Raw embedding vector (alternative to memory_id)"}),
"galaxy": str_prop("Galaxy filter (optional)"),
"limit": int_prop("Maximum results (default 10)"),
}),
&["memory_id"],
)
}
fn name(&self) -> &str {
"memory.vector.search"
}
fn gana(&self) -> Gana {
Gana::WinnowingBasket
}
fn effects(&self) -> &EffectRow {
&self.effects
}
async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
self.ensure_loaded()?;
let limit = args
.get("limit")
.and_then(serde_json::Value::as_u64)
.unwrap_or(10) as usize;
let galaxy_str = args.get("galaxy").and_then(|v| v.as_str());
let galaxy_filter = match galaxy_str {
Some(g) => Some(parse_galaxy(g)?),
None => None,
};
let results = if let Some(id_str) = args.get("memory_id").and_then(|v| v.as_str()) {
let memory_id = uuid::Uuid::parse_str(id_str).map_err(|e| {
wm_core::CoreError::InvalidArgs(format!("Invalid memory_id UUID: {e}"))
})?;
let vs = self
.vector_store
.lock()
.map_err(|e| wm_core::CoreError::Tool(format!("vector store lock: {e}")))?;
vs.search_similar_to(memory_id, limit)
} else if let Some(embedding_arr) = args.get("embedding").and_then(|v| v.as_array()) {
let embedding: Vec<f32> = embedding_arr
.iter()
.filter_map(|v| v.as_f64().map(|f| f as f32))
.collect();
if embedding.is_empty() {
return Err(wm_core::CoreError::InvalidArgs(
"embedding (array of numbers) or memory_id (string) required".into(),
));
}
let vs = self
.vector_store
.lock()
.map_err(|e| wm_core::CoreError::Tool(format!("vector store lock: {e}")))?;
vs.search(&embedding, limit, galaxy_filter)
} else {
return Err(wm_core::CoreError::InvalidArgs(
"Either 'embedding' (array of floats) or 'memory_id' (UUID string) is required"
.into(),
));
};
let entries: Vec<Value> = results
.iter()
.filter_map(|r| {
let stored = self.store.get(r.galaxy, r.memory_id).ok().flatten();
if let Some(mem) = &stored {
if !crate::expansion::common::mcp_visible(mem) {
return None;
}
if !crate::expansion::common::validity_visible(mem) {
return None;
}
}
let preview = stored
.map(|m| m.content.chars().take(120).collect::<String>())
.unwrap_or_default();
Some(json!({
"memory_id": r.memory_id.to_string(),
"galaxy": r.galaxy.db_name(),
"score": r.score,
"content_preview": preview,
}))
})
.collect();
Ok(json!({
"status": "success",
"total": entries.len(),
"results": entries,
}))
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct MemoryAssociateTool {
store: Arc<MemoryStore>,
stats: ToolStats,
effects: EffectRow,
}
impl MemoryAssociateTool {
pub fn new(store: Arc<MemoryStore>) -> Self {
Self {
store,
stats: ToolStats::default(),
effects: EffectRow {
writes: vec![Resource::Galaxy("associations".into())],
invokes: vec![Capability::MemoryWrite],
..Default::default()
},
}
}
}
#[async_trait]
impl Tool for MemoryAssociateTool {
fn name(&self) -> &str {
"memory.associate"
}
fn gana(&self) -> Gana {
Gana::Net
}
fn effects(&self) -> &EffectRow {
&self.effects
}
fn input_schema(&self) -> Value {
schema(
&json!({
"source": str_prop("Source memory UUID"),
"target": str_prop("Target memory UUID"),
"type": str_prop("Link type (default: related)"),
"weight": num_prop("Association weight (default 1.0)"),
}),
&["source", "target"],
)
}
async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
let source_str = args.get("source").and_then(|v| v.as_str()).ok_or_else(|| {
wm_core::CoreError::InvalidArgs("source (UUID string) required".into())
})?;
let target_str = args.get("target").and_then(|v| v.as_str()).ok_or_else(|| {
wm_core::CoreError::InvalidArgs("target (UUID string) required".into())
})?;
let source = uuid::Uuid::parse_str(source_str)
.map_err(|e| wm_core::CoreError::InvalidArgs(format!("invalid source UUID: {e}")))?;
let target = uuid::Uuid::parse_str(target_str)
.map_err(|e| wm_core::CoreError::InvalidArgs(format!("invalid target UUID: {e}")))?;
let weight = args
.get("weight")
.and_then(serde_json::Value::as_f64)
.unwrap_or(1.0) as f32;
let assoc_type = args
.get("type")
.and_then(|v| v.as_str())
.unwrap_or("related");
let link_type = wm_memory::LinkType::from_str_lossy(assoc_type);
let assoc = Association::new(source, target, link_type, weight);
let assoc_store = AssociationStore::open(self.store.env())?;
assoc_store.put(self.store.env(), &assoc)?;
Ok(json!({
"status": "success",
"source": source_str,
"target": target_str,
"weight": weight,
}))
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct MemoryAssociationsTool {
store: Arc<MemoryStore>,
stats: ToolStats,
effects: EffectRow,
}
impl MemoryAssociationsTool {
pub fn new(store: Arc<MemoryStore>) -> Self {
Self {
store,
stats: ToolStats::default(),
effects: EffectRow::read_only(vec![Resource::Galaxy("associations".into())]),
}
}
}
#[async_trait]
impl Tool for MemoryAssociationsTool {
fn name(&self) -> &str {
"memory.associations"
}
fn gana(&self) -> Gana {
Gana::Net
}
fn effects(&self) -> &EffectRow {
&self.effects
}
fn input_schema(&self) -> Value {
schema(
&json!({
"id": str_prop("Memory UUID to inspect"),
"direction": str_prop("Direction: from | to | both (default: both)"),
}),
&["id"],
)
}
async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
let id_str = args
.get("id")
.and_then(|v| v.as_str())
.ok_or_else(|| wm_core::CoreError::InvalidArgs("id (UUID string) required".into()))?;
let id = uuid::Uuid::parse_str(id_str)
.map_err(|e| wm_core::CoreError::InvalidArgs(format!("invalid UUID: {e}")))?;
let direction = args
.get("direction")
.and_then(|v| v.as_str())
.unwrap_or("both");
let assoc_store = AssociationStore::open(self.store.env())?;
let mut entries = Vec::new();
if direction == "from" || direction == "both" {
for a in assoc_store.find_from(self.store.env(), id)? {
entries.push(json!({
"source": a.source.to_string(),
"target": a.target.to_string(),
"weight": a.weight,
"link_type": a.link_type.as_str(),
"co_activation_count": a.co_activation_count,
"direction": "outgoing",
}));
}
}
if direction == "to" || direction == "both" {
for a in assoc_store.find_to(self.store.env(), id)? {
entries.push(json!({
"source": a.source.to_string(),
"target": a.target.to_string(),
"weight": a.weight,
"link_type": a.link_type.as_str(),
"co_activation_count": a.co_activation_count,
"direction": "incoming",
}));
}
}
let total = assoc_store.count(self.store.env())?;
Ok(json!({
"status": "success",
"id": id_str,
"direction": direction,
"associations": entries,
"returned": entries.len(),
"total_in_store": total,
}))
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct KarmaReportTool {
ledger: Arc<KarmaLedger>,
stats: ToolStats,
effects: EffectRow,
}
impl KarmaReportTool {
pub fn new(ledger: Arc<KarmaLedger>) -> Self {
Self {
ledger,
stats: ToolStats::default(),
effects: EffectRow::read_only(vec![Resource::Galaxy("karma".into())]),
}
}
}
#[async_trait]
impl Tool for KarmaReportTool {
fn name(&self) -> &str {
"karma.report"
}
fn gana(&self) -> Gana {
Gana::Willow
}
fn effects(&self) -> &EffectRow {
&self.effects
}
async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
let recent_count = args
.get("limit")
.and_then(serde_json::Value::as_u64)
.unwrap_or(10) as usize;
let recent = self.ledger.recent(recent_count)?;
let tool_debt = self.ledger.tool_debt()?;
let recent_entries: Vec<Value> = recent
.iter()
.map(|e| {
json!({
"id": e.id,
"tool": e.tool,
"success": e.success,
"mismatch": e.mismatch,
"debt_delta": e.debt_delta,
"guna": format!("{:?}", e.guna),
"total_debt": e.total_debt,
})
})
.collect();
let tool_debt_entries: Vec<Value> = tool_debt
.iter()
.map(|(tool, debt)| {
json!({
"tool": tool,
"debt": debt,
})
})
.collect();
Ok(json!({
"status": "success",
"total_debt": self.ledger.total_debt(),
"chain_head": self.ledger.chain_head(),
"entry_count": self.ledger.next_id(),
"recent_entries": recent_entries,
"per_tool_debt": tool_debt_entries,
}))
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct DharmaStatusTool {
gate: Arc<DharmaGate>,
stats: ToolStats,
effects: EffectRow,
}
impl DharmaStatusTool {
pub fn new(gate: Arc<DharmaGate>) -> Self {
Self {
gate,
stats: ToolStats::default(),
effects: EffectRow::pure(),
}
}
}
#[async_trait]
impl Tool for DharmaStatusTool {
fn name(&self) -> &str {
"dharma.status"
}
fn gana(&self) -> Gana {
Gana::ExtendedNet
}
fn effects(&self) -> &EffectRow {
&self.effects
}
async fn call(&self, _ctx: &mut Context, _args: Value) -> wm_core::Result<Value> {
let homeostasis = self.gate.homeostasis();
let health = homeostasis.health_score();
let decisions = wm_governance::dharma_gate::verdict_counts();
Ok(json!({
"status": "success",
"homeostasis": {
"cpu_load": homeostasis.cpu_load,
"memory_pressure": homeostasis.memory_pressure,
"active": homeostasis.active,
"health_score": health,
"stressed": homeostasis.is_stressed(),
},
"decisions": {
"observe": decisions.observe,
"advise": decisions.advise,
"correct": decisions.correct,
"intervene": decisions.intervene,
"panic": decisions.panic,
"total": decisions.total(),
"blocked": decisions.blocked(),
"blocked_ratio": decisions.blocked_ratio(),
},
"sutras": {
"ahimsa": "Non-harm — destructive actions blocked in strict mode",
"satya": "Truth — memory fabrication always forbidden",
},
}))
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct HarmonyVectorTool {
monitor: Arc<SubstrateMonitor>,
stats: ToolStats,
effects: EffectRow,
}
impl HarmonyVectorTool {
pub fn new(monitor: Arc<SubstrateMonitor>) -> Self {
Self {
monitor,
stats: ToolStats::default(),
effects: EffectRow::pure(),
}
}
}
#[async_trait]
impl Tool for HarmonyVectorTool {
fn name(&self) -> &str {
"harmony.vector"
}
fn gana(&self) -> Gana {
Gana::Dipper
}
fn effects(&self) -> &EffectRow {
&self.effects
}
async fn call(&self, _ctx: &mut Context, _args: Value) -> wm_core::Result<Value> {
let hv = self.monitor.sample();
Ok(json!({
"status": "success",
"harmony_vector": hv.to_json(),
}))
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct HarmonyHistoryTool {
monitor: Arc<SubstrateMonitor>,
stats: ToolStats,
effects: EffectRow,
}
impl HarmonyHistoryTool {
pub fn new(monitor: Arc<SubstrateMonitor>) -> Self {
Self {
monitor,
stats: ToolStats::default(),
effects: EffectRow::pure(),
}
}
}
#[async_trait]
impl Tool for HarmonyHistoryTool {
fn name(&self) -> &str {
"harmony.history"
}
fn gana(&self) -> Gana {
Gana::Dipper
}
fn effects(&self) -> &EffectRow {
&self.effects
}
async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
let limit = args.get("limit").and_then(Value::as_u64).unwrap_or(20) as usize;
let samples: Vec<Value> = self
.monitor
.history(limit)
.iter()
.map(wm_substrate::HarmonyVector::to_json)
.collect();
Ok(json!({
"status": "success",
"count": samples.len(),
"samples": samples,
}))
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct GnosisStatusTool {
dharma_gate: Arc<DharmaGate>,
resource_rules: Arc<ResourceRules>,
substrate: Arc<SubstrateMonitor>,
stats: ToolStats,
effects: EffectRow,
}
impl GnosisStatusTool {
pub fn new(
dharma_gate: Arc<DharmaGate>,
resource_rules: Arc<ResourceRules>,
substrate: Arc<SubstrateMonitor>,
) -> Self {
Self {
dharma_gate,
resource_rules,
substrate,
stats: ToolStats::default(),
effects: EffectRow::pure(),
}
}
}
#[async_trait]
impl Tool for GnosisStatusTool {
fn input_schema(&self) -> Value {
schema(&json!({}), &[])
}
fn name(&self) -> &str {
"gnosis.status"
}
fn gana(&self) -> Gana {
Gana::ThreeStars
}
fn effects(&self) -> &EffectRow {
&self.effects
}
async fn call(&self, ctx: &mut Context, _args: Value) -> wm_core::Result<Value> {
let homeostasis = self.dharma_gate.homeostasis();
let health = homeostasis.health_score();
let budget_usage = self.resource_rules.budget_usage();
let human_approved = self.resource_rules.human_approved();
let last_hv = self.substrate.last_sample();
Ok(json!({
"status": "success",
"brain_wave": format!("{:?}", ctx.brain_wave),
"homeostasis": {
"cpu_load": homeostasis.cpu_load,
"memory_pressure": homeostasis.memory_pressure,
"active": homeostasis.active,
"health_score": health,
"stressed": homeostasis.is_stressed(),
},
"resource_rules": {
"writes_last_minute": budget_usage.writes_last_minute,
"spawns_last_minute": budget_usage.spawns_last_minute,
"network_last_minute": budget_usage.network_last_minute,
"novelty_entries": budget_usage.novelty_entries,
"human_approved": human_approved,
"require_human_review": true,
},
"substrate": last_hv.as_ref().map(wm_substrate::HarmonyVector::to_json),
"governance_layers": {
"lakshmi": "Harmony Vector — hardware awareness (active)",
"tiferet": "Resource Gating — brain-wave transitions gated by health (active)",
"yama": "Dharma Resource Rules — budgets, novelty, purpose, human review (active)",
"gnosis": "Transparency Portals — this tool (active)",
},
}))
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct GnosisHistoryTool {
substrate: Arc<SubstrateMonitor>,
stats: ToolStats,
effects: EffectRow,
}
impl GnosisHistoryTool {
pub fn new(substrate: Arc<SubstrateMonitor>) -> Self {
Self {
substrate,
stats: ToolStats::default(),
effects: EffectRow::pure(),
}
}
}
#[async_trait]
impl Tool for GnosisHistoryTool {
fn input_schema(&self) -> Value {
schema(
&json!({
"limit": int_prop("Maximum history entries (default 20)"),
}),
&[],
)
}
fn name(&self) -> &str {
"gnosis.history"
}
fn gana(&self) -> Gana {
Gana::ThreeStars
}
fn effects(&self) -> &EffectRow {
&self.effects
}
async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
let limit = args.get("limit").and_then(Value::as_u64).unwrap_or(20) as usize;
let history = self.substrate.history(limit);
let samples: Vec<Value> = history
.iter()
.map(wm_substrate::HarmonyVector::to_json)
.collect();
let avg_cpu = if samples.is_empty() {
0.0
} else {
samples
.iter()
.filter_map(|s| s["cpu_load"].as_f64())
.sum::<f64>()
/ samples.len() as f64
};
let avg_mem = if samples.is_empty() {
0.0
} else {
samples
.iter()
.filter_map(|s| s["memory_pressure"].as_f64())
.sum::<f64>()
/ samples.len() as f64
};
let avg_health = if samples.is_empty() {
0.0
} else {
samples
.iter()
.filter_map(|s| s["health_score"].as_f64())
.sum::<f64>()
/ samples.len() as f64
};
Ok(json!({
"status": "success",
"count": samples.len(),
"summary": {
"avg_cpu_load": avg_cpu,
"avg_memory_pressure": avg_mem,
"avg_health_score": avg_health,
},
"samples": samples,
}))
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct GnosisExplainTool {
dharma_gate: Arc<DharmaGate>,
resource_rules: Arc<ResourceRules>,
stats: ToolStats,
effects: EffectRow,
}
impl GnosisExplainTool {
pub fn new(dharma_gate: Arc<DharmaGate>, resource_rules: Arc<ResourceRules>) -> Self {
Self {
dharma_gate,
resource_rules,
stats: ToolStats::default(),
effects: EffectRow::pure(),
}
}
}
#[async_trait]
impl Tool for GnosisExplainTool {
fn input_schema(&self) -> Value {
schema(
&json!({
"tool_name": str_prop("Tool name to explain"),
"is_write": bool_prop("Claim: the invocation writes"),
"is_spawn": bool_prop("Claim: the invocation spawns a process"),
"is_network": bool_prop("Claim: the invocation uses the network"),
"has_purpose": bool_prop("Claim: the invocation carries a purpose"),
"args_hash": str_prop("Hash of the arguments under evaluation"),
}),
&[],
)
}
fn name(&self) -> &str {
"gnosis.explain"
}
fn gana(&self) -> Gana {
Gana::ThreeStars
}
fn effects(&self) -> &EffectRow {
&self.effects
}
async fn call(&self, ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
let tool_name = args
.get("tool_name")
.and_then(Value::as_str)
.unwrap_or("unknown");
let is_write = args
.get("is_write")
.and_then(Value::as_bool)
.unwrap_or(false);
let is_spawn = args
.get("is_spawn")
.and_then(Value::as_bool)
.unwrap_or(false);
let is_network = args
.get("is_network")
.and_then(Value::as_bool)
.unwrap_or(false);
let has_purpose = args
.get("has_purpose")
.and_then(Value::as_bool)
.unwrap_or(true);
let args_hash = args.get("args_hash").and_then(Value::as_u64).unwrap_or(0);
let homeostasis = self.dharma_gate.homeostasis();
let dummy_effects = if is_write {
EffectRow {
writes: vec![Resource::Filesystem],
..Default::default()
}
} else {
EffectRow::pure()
};
let dharma_verdict = self.dharma_gate.evaluate(&dummy_effects, ctx);
let resource_verdict = self.resource_rules.evaluate(
tool_name,
args_hash,
is_write,
is_spawn,
is_network,
has_purpose,
&homeostasis,
ctx.brain_wave,
);
Ok(json!({
"status": "success",
"tool_name": tool_name,
"brain_wave": format!("{:?}", ctx.brain_wave),
"homeostasis": {
"cpu_load": homeostasis.cpu_load,
"memory_pressure": homeostasis.memory_pressure,
"health_score": homeostasis.health_score(),
"stressed": homeostasis.is_stressed(),
},
"dharma_verdict": {
"verdict": format!("{:?}", dharma_verdict),
"blocks": dharma_verdict.blocks(),
"reason": dharma_verdict.reason(),
},
"resource_verdict": {
"verdict": format!("{:?}", resource_verdict),
"blocks": resource_verdict.blocks(),
"reason": resource_verdict.reason(),
},
"would_block": dharma_verdict.blocks() || resource_verdict.blocks(),
"explanation": format!(
"Tool '{}' under {:?} brain-wave with health {:.2}: Dharma says '{}', Resources say '{}'. {}",
tool_name,
ctx.brain_wave,
homeostasis.health_score(),
dharma_verdict.reason(),
resource_verdict.reason(),
if dharma_verdict.blocks() || resource_verdict.blocks() {
"Action would be BLOCKED."
} else {
"Action would be ALLOWED."
}
),
}))
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
pub struct WmMetaTool {
registry: Arc<ToolRegistry>,
stats: ToolStats,
effects: EffectRow,
embedding_router: Option<Arc<embedding_router::EmbeddingRouter>>,
shadow_stats: Arc<std::sync::RwLock<embedding_router::ShadowModeStats>>,
pipeline: Option<Arc<DispatchPipeline>>,
}
impl WmMetaTool {
#[must_use]
pub fn new(registry: Arc<ToolRegistry>) -> Self {
Self {
registry,
stats: ToolStats::default(),
effects: EffectRow::pure(),
embedding_router: None,
shadow_stats: Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
pipeline: None,
}
}
#[must_use]
pub fn with_embedder(
registry: Arc<ToolRegistry>,
embedder: Box<dyn wm_memory::Embedder>,
) -> Self {
let embedding_router = Self::build_embedding_router(®istry, embedder).map(Arc::new);
Self {
registry,
stats: ToolStats::default(),
effects: EffectRow::pure(),
embedding_router,
shadow_stats: Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
pipeline: None,
}
}
#[must_use]
pub fn with_embedder_and_shadow_stats(
registry: Arc<ToolRegistry>,
embedder: Box<dyn wm_memory::Embedder>,
shadow_stats: Arc<std::sync::RwLock<embedding_router::ShadowModeStats>>,
) -> Self {
let embedding_router = Self::build_embedding_router(®istry, embedder).map(Arc::new);
Self {
registry,
stats: ToolStats::default(),
effects: EffectRow::pure(),
embedding_router,
shadow_stats,
pipeline: None,
}
}
#[must_use]
pub fn with_router_shadow_stats_and_pipeline(
registry: Arc<ToolRegistry>,
embedder: Box<dyn wm_memory::Embedder>,
shadow_stats: Arc<std::sync::RwLock<embedding_router::ShadowModeStats>>,
pipeline: Option<Arc<DispatchPipeline>>,
) -> Self {
let embedding_router = Self::build_embedding_router(®istry, embedder).map(Arc::new);
Self {
registry,
stats: ToolStats::default(),
effects: EffectRow::pure(),
embedding_router,
shadow_stats,
pipeline,
}
}
fn build_embedding_router(
registry: &ToolRegistry,
embedder: Box<dyn wm_memory::Embedder>,
) -> Option<embedding_router::EmbeddingRouter> {
let tools = registry.all_ref();
if tools.is_empty() {
return embedding_router::EmbeddingRouter::new(embedder);
}
let descriptions = embedding_router::anchored_descriptions(tools);
embedding_router::EmbeddingRouter::with_descriptions(embedder, descriptions)
}
fn classify(text: &str) -> (&'static str, f64) {
nlu::classify(text)
}
fn classify_with_router_inner(
router: &embedding_router::EmbeddingRouter,
shadow_stats: &std::sync::RwLock<embedding_router::ShadowModeStats>,
text: &str,
) -> (String, f64, Option<Vec<f32>>) {
let (emb_tool, emb_conf, margin, query_emb) =
match router.route_with_margin_and_embedding(text) {
Some(t) => t,
None => ("gnosis".into(), 0.0, 0.0, Vec::new()),
};
let (tfidf_tool, tfidf_conf) = nlu::classify(text);
if emb_tool != tfidf_tool {
tracing::debug!(
query = text.chars().take(100).collect::<String>(),
embedding_tool = %emb_tool,
embedding_conf = emb_conf,
margin = margin,
tfidf_tool = %tfidf_tool,
tfidf_conf = tfidf_conf,
"shadow mode disagreement: embedding vs TF-IDF"
);
}
if let Ok(mut stats) = shadow_stats.write() {
stats.record(text, &emb_tool, emb_conf, tfidf_tool, tfidf_conf);
}
let selected = if margin < embedding_router::MIN_MARGIN {
(tfidf_tool.to_string(), tfidf_conf)
} else {
(emb_tool, emb_conf)
};
let query_emb = (!query_emb.is_empty()).then_some(query_emb);
(selected.0, selected.1, query_emb)
}
async fn classify_async(&self, text: &str) -> (String, f64, Option<Vec<f32>>) {
let Some(router) = self.embedding_router.clone() else {
let (tool, conf) = Self::classify(text);
return (tool.to_string(), conf, None);
};
let shadow_stats = Arc::clone(&self.shadow_stats);
let text_owned = text.to_string();
let fallback_text = text_owned.clone();
match tokio::task::spawn_blocking(move || {
Self::classify_with_router_inner(&router, &shadow_stats, &text_owned)
})
.await
{
Ok(result) => result,
Err(join_err) => {
tracing::warn!(
error = %join_err,
"NLU blocking classifier task failed — falling back to TF-IDF"
);
let (tool, conf) = Self::classify(&fallback_text);
(tool.to_string(), conf, None)
}
}
}
#[must_use]
pub const fn shadow_stats(&self) -> &Arc<std::sync::RwLock<embedding_router::ShadowModeStats>> {
&self.shadow_stats
}
#[must_use]
pub const fn embedding_router(&self) -> Option<&Arc<embedding_router::EmbeddingRouter>> {
self.embedding_router.as_ref()
}
fn required_arg(tool_name: &str) -> Option<&'static str> {
match tool_name {
"memory.create" => Some("content"),
"memory.batch_create" => Some("items"),
"memory.read" => Some("id"),
"memory.delete" => Some("id"),
"memory.search" => Some("query"),
"memory.episodic_search" => Some("query"),
"memory.associate" => Some("source"),
"memory.associations" => Some("id"),
"memory.update" => Some("id"),
"memory.revisions" => Some("id"),
"memory.tag" => Some("id"),
"memory.batch_read" => Some("ids"),
"memory.nearby" => Some("query"),
"session.end" => Some("session_id"),
"agent.register" => Some("name"),
"agent.trust" => Some("agent_id"),
"agent.descriptions" => Some("agent_id"),
"agent.capabilities" => Some("agent_id"),
"agent.heartbeat.history" => Some("agent_id"),
"agent.deregister" => Some("agent_id"),
"galaxy.purge" => Some("galaxy"),
"memory.deduplicate" => Some("galaxy"),
"task.distribute" => Some("task"),
"code.claim" => Some("scope"),
"code.check" => Some("scope"),
"code.release" => Some("scope"),
_ => None,
}
}
fn missing_arg_hint(tool_name: &str, missing: &str) -> String {
match (tool_name, missing) {
("memory.create", "content") => "Provide the content to store, e.g. wm(thought='remember that rust is fast')".into(),
("memory.read", "id") => "Provide a memory UUID, e.g. wm(route='memory.read', args={\"id\": \"<uuid>\"}). To search by content instead, use wm(thought='find <text>') or wm(route='memory.search', args={\"query\": \"...\"}). To list memories, use wm(route='memory.list', args={\"galaxy\": \"codex\", \"limit\": 10})".into(),
("memory.delete", "id") => "Provide a memory UUID, e.g. wm(thought='delete memory <uuid>')".into(),
("memory.search", "query") => "Provide a search query, e.g. wm(thought='search for rust')".into(),
("memory.query", "query") => "memory.query accepts `query` as optional when filtering by tags/importance/dates, e.g. wm(route='memory.query', args={\"tags\": [\"project:myapp\"]})".into(),
("memory.vector.search", "memory_id") => "Provide a memory UUID for similarity search, e.g. wm(route='memory.vector.search', args={\"memory_id\": \"<uuid>\"})".into(),
("memory.update", "id") => "Provide a memory UUID to update, e.g. wm(route='memory.update', args={\"id\": \"<uuid>\", \"tags\": [\"new\"]})".into(),
("memory.revisions", "id") => "Provide a memory UUID to inspect, e.g. wm(route='memory.revisions', args={\"id\": \"<uuid>\", \"action\": \"verify\"}) — actions: list (default) | verify".into(),
("memory.tag", "id") => "Provide a memory UUID to tag, e.g. wm(route='memory.tag', args={\"id\": \"<uuid>\", \"tags\": [\"rust\"]})".into(),
_ => format!("Missing required argument: '{missing}' for tool '{tool_name}'"),
}
}
fn extract_payload(thought: &str, tool_name: &str) -> Option<(String, String)> {
let lower = thought.to_lowercase();
match tool_name {
"memory.create" => {
for prefix in &[
"remember that ",
"remember ",
"store ",
"save ",
"note that ",
"note ",
] {
if lower.starts_with(prefix) {
let content = thought[prefix.len()..].to_string();
if !content.is_empty() {
return Some(("content".into(), content));
}
}
}
if !thought.is_empty() {
return Some(("content".into(), thought.to_string()));
}
}
"memory.read" => {
for prefix in &["recall ", "read memory ", "fetch memory ", "get memory "] {
if lower.starts_with(prefix) {
let id = thought[prefix.len()..].trim().to_string();
if !id.is_empty() {
return Some(("id".into(), id));
}
}
}
}
"memory.list" => {
for prefix in &[
"list memories",
"show memories",
"search memories",
"search for",
] {
if lower.contains(prefix) {
let after = &thought[lower.find(prefix).unwrap() + prefix.len()..];
let query = after.trim().trim_start_matches("in ").trim();
if !query.is_empty() {
return Some(("galaxy".into(), query.to_string()));
}
}
}
}
"memory.delete" => {
for prefix in &["delete memory ", "remove memory ", "forget memory "] {
if lower.starts_with(prefix) {
let id = thought[prefix.len()..].trim().to_string();
if !id.is_empty() {
return Some(("id".into(), id));
}
}
}
}
"memory.search" => {
let mut text: &str = thought;
if let Some((phrase, _, _)) = crate::nlu::PHRASE_ROUTES
.iter()
.find(|(phrase, tool, _)| *tool == "memory.search" && lower.starts_with(phrase))
{
text = &thought[phrase.len()..];
} else if lower.starts_with("search for ") {
text = &thought["search for ".len()..];
} else if lower.starts_with("search ") {
text = &thought["search ".len()..];
} else {
for (verb, tool, _) in crate::nlu::PREFIX_ROUTES {
if *tool != "memory.search" {
continue;
}
if let Some(rest) = lower.strip_prefix(verb) {
if rest.is_empty() || rest.starts_with(' ') || rest.starts_with(':') {
text = thought[verb.len()..].trim_start_matches([' ', ':']);
break;
}
}
}
}
let lower_text = text.to_lowercase();
for filler in ["memory for ", "memories for ", "memory ", "memories "] {
if lower_text.starts_with(filler) {
text = &text[filler.len()..];
break;
}
}
let query = text
.trim()
.trim_end_matches(['?', '!'])
.trim()
.trim_end_matches(" in memory")
.trim();
if !query.is_empty() {
return Some(("query".into(), query.to_string()));
}
}
"memory.chat" => {
for prefix in &[
"chat about ",
"chat ",
"ask about ",
"ask ",
"discuss ",
"explore ",
"converse about ",
] {
if lower.starts_with(prefix) {
let query = thought[prefix.len()..].trim().to_string();
if !query.is_empty() {
return Some(("query".into(), query));
}
}
}
if !thought.is_empty() {
return Some(("query".into(), thought.to_string()));
}
}
"memory.vector.search" => {
for prefix in &[
"find similar to ",
"similar to memory ",
"vector search ",
"semantic search ",
"embedding search ",
] {
if lower.starts_with(prefix) {
let id = thought[prefix.len()..].trim().to_string();
if !id.is_empty() {
return Some(("memory_id".into(), id));
}
}
}
}
"memory.count" => {
for prefix in &[
"count memories in ",
"how many memories in ",
"memory count ",
] {
if lower.starts_with(prefix) {
let galaxy = thought[prefix.len()..].trim().to_string();
if !galaxy.is_empty() {
return Some(("galaxy".into(), galaxy));
}
}
}
}
"session.start" => {
for prefix in &["start session ", "new session ", "begin session "] {
if lower.starts_with(prefix) {
let title = thought[prefix.len()..].trim().to_string();
if !title.is_empty() {
return Some(("title".into(), title));
}
}
}
}
"session.end" => {
for prefix in &["end session ", "close session ", "stop session "] {
if lower.starts_with(prefix) {
let id = thought[prefix.len()..].trim().to_string();
if !id.is_empty() {
return Some(("session_id".into(), id));
}
}
}
}
"agent.register" => {
for prefix in &[
"register agent ",
"new agent ",
"create agent ",
"add agent ",
] {
if lower.starts_with(prefix) {
let name = thought[prefix.len()..].trim().to_string();
if !name.is_empty() {
return Some(("name".into(), name));
}
}
}
}
"agent.trust"
| "agent.descriptions"
| "agent.capabilities"
| "agent.heartbeat.history"
| "agent.deregister" => {
for prefix in &[
"trust agent ",
"describe agent ",
"capabilities agent ",
"heartbeat history agent ",
"deregister agent ",
"unregister agent ",
"remove agent ",
] {
if lower.starts_with(prefix) {
let id = thought[prefix.len()..].trim().to_string();
if !id.is_empty() {
return Some(("agent_id".into(), id));
}
}
}
}
"galaxy.purge" => {
for prefix in &["purge galaxy ", "wipe galaxy ", "clear galaxy "] {
if lower.starts_with(prefix) {
let galaxy = thought[prefix.len()..].trim().to_string();
if !galaxy.is_empty() {
return Some(("galaxy".into(), galaxy));
}
}
}
}
"task.distribute" => {
for prefix in &["distribute task ", "assign task ", "dispatch task "] {
if lower.starts_with(prefix) {
let task = thought[prefix.len()..].trim().to_string();
if !task.is_empty() {
return Some(("task".into(), task));
}
}
}
}
"memory.sort" => {
for prefix in &["sort memories ", "sort memory ", "order memories "] {
if lower.starts_with(prefix) {
let galaxy = thought[prefix.len()..].trim().to_string();
if !galaxy.is_empty() {
return Some(("galaxy".into(), galaxy));
}
}
}
}
"memory.filter" => {
for prefix in &["filter memories ", "filter memory "] {
if lower.starts_with(prefix) {
let galaxy = thought[prefix.len()..].trim().to_string();
if !galaxy.is_empty() {
return Some(("galaxy".into(), galaxy));
}
}
}
}
"memory.deduplicate" => {
for prefix in &[
"deduplicate memories ",
"deduplicate memory ",
"dedup memories ",
] {
if lower.starts_with(prefix) {
let galaxy = thought[prefix.len()..].trim().to_string();
if !galaxy.is_empty() {
return Some(("galaxy".into(), galaxy));
}
}
}
}
"memory.export" => {
for prefix in &["export memories ", "export memory "] {
if lower.starts_with(prefix) {
let galaxy = thought[prefix.len()..].trim().to_string();
if !galaxy.is_empty() {
return Some(("galaxy".into(), galaxy));
}
}
}
}
"speculative.decode" => {
for prefix in &[
"speculative decode ",
"speculative ",
"decode ",
"draft and verify ",
"accelerate inference ",
] {
if lower.starts_with(prefix) {
let prompt = thought[prefix.len()..].trim().to_string();
if !prompt.is_empty() {
return Some(("prompt".into(), prompt));
}
}
}
}
"meta.enhance" => {
for prefix in &[
"enhance ",
"enhance prompt ",
"grounded inference ",
"self-correct ",
"meta enhance ",
"cognitive enhance ",
"augment ",
] {
if lower.starts_with(prefix) {
let prompt = thought[prefix.len()..].trim().to_string();
if !prompt.is_empty() {
return Some(("prompt".into(), prompt));
}
}
}
}
"dense.encode" => {
for prefix in &["dense encode ", "compress ", "encode ", "compact "] {
if lower.starts_with(prefix) {
let text = thought[prefix.len()..].trim().to_string();
if !text.is_empty() {
return Some(("text".into(), text));
}
}
}
}
"dream.trigger" => {
for prefix in &[
"dream trigger ",
"trigger dream ",
"start dream ",
"force dream ",
"initiate dream ",
] {
if lower.starts_with(prefix) {
let rest = thought[prefix.len()..].trim();
if !rest.is_empty() {
return Some(("force".into(), rest.to_string()));
}
}
}
}
_ => {}
}
None
}
}
#[async_trait]
impl Tool for WmMetaTool {
fn input_schema(&self) -> Value {
schema(
&json!({
"route": str_prop("Explicit canonical route, e.g. \"memory.search\" (preferred for agents)"),
"thought": str_prop("Natural-language convenience routing (least reliable; prefer route)"),
"args": json!({"type": "object", "description": "Arguments passed through to the target tool"}),
}),
&[],
)
}
fn name(&self) -> &str {
"wm"
}
fn gana(&self) -> Gana {
Gana::Horn
}
fn effects(&self) -> &EffectRow {
&self.effects
}
async fn call(&self, ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
let thought = args.get("thought").and_then(|v| v.as_str()).unwrap_or("");
let (route, passthrough_args) = if glyph_mode_from_env() {
if let Some((r, a)) = decode_lkep(&args) {
(Some(r), a)
} else if let Some(Value::Object(map)) = decode_glyph(&args) {
(
map.get("route").and_then(Value::as_str).map(String::from),
map.get("args").cloned().unwrap_or(Value::Null),
)
} else {
let r = args
.get("route")
.and_then(Value::as_str)
.map(|s| resolve_route(s).unwrap_or(s).to_string());
let a = args.get("args").cloned().unwrap_or(Value::Null);
(r, a)
}
} else {
(
args.get("route").and_then(Value::as_str).map(|s| {
expansion::common::canonical_tool_alias(s)
.unwrap_or(s)
.to_string()
}),
args.get("args").cloned().unwrap_or(Value::Null),
)
};
let route = route.as_deref();
if thought.is_empty() && route.is_none() {
let received: Vec<String> = args
.as_object()
.map(|o| o.keys().cloned().collect())
.unwrap_or_default();
let detail = if received.is_empty() {
String::new()
} else {
format!("; received argument keys: {received:?}")
};
return Ok(json!({
"status": "error",
"message": format!(
"Either 'thought' (natural language) or 'route' (explicit) is required{detail}"
),
"hint": "wm(thought='remember that X is Y') or wm(route='memory.create', args={\"content\": \"...\"})"
}));
}
let (tool_name, confidence, query_emb) = if let Some(r) = route {
(r.to_string(), 1.0, None)
} else {
self.classify_async(thought).await
};
if route.is_none() && tool_name == "gnosis" && confidence < NLU_ABSTENTION_THRESHOLD {
let alternative = crate::nlu::classify_with_alternative(thought).2;
let mut meta = json!({
"tool": tool_name,
"confidence": confidence,
"abstained": true
});
if let Some((alt_tool, alt_confidence)) = alternative {
meta["suggested_route"] = json!(alt_tool);
meta["suggested_confidence"] = json!(alt_confidence);
}
return Ok(json!({
"status": "error",
"message": "Could not confidently match your request to a tool.",
"confidence": confidence,
"hint": "Use explicit routing: wm(route='tool.name', args={...}). Use wm(route='tools.list') to see available tools.",
"_wm_route": meta
}));
}
let mut route_meta = json!({ "tool": tool_name, "confidence": confidence });
if route.is_none() && confidence < NLU_LOW_CONFIDENCE {
route_meta["low_confidence"] = json!(true);
if let (_, _, Some((alt_tool, alt_confidence))) =
crate::nlu::classify_with_alternative(thought)
{
route_meta["alternative_route"] = json!(alt_tool);
route_meta["alternative_confidence"] = json!(alt_confidence);
}
}
let mut tool_args = if passthrough_args.is_object() {
let mut args = passthrough_args;
if let Some(obj) = args.as_object_mut() {
obj.remove("_meta");
}
args
} else {
Value::Null
};
if route.is_none() && !thought.is_empty() && tool_args.is_null() {
if let Some((param, value)) = Self::extract_payload(thought, &tool_name) {
tool_args = json!({ param: value });
}
}
let tool = self.registry.get(&tool_name);
match tool {
Some(t) => {
if route.is_none() && t.effects().destructive {
return Ok(json!({
"status": "error",
"message": format!(
"tool '{tool_name}' is destructive and cannot be reached via natural language — use wm(route='{tool_name}', args={{...}}) with \"confirm\": true"
),
"_wm_route": route_meta.clone(),
}));
}
if let Some(required) = Self::required_arg(&tool_name) {
let has_arg = tool_args.is_object()
&& tool_args.get(required).is_some()
&& !tool_args
.get(required)
.is_some_and(serde_json::Value::is_null);
if !has_arg {
return Ok(json!({
"status": "error",
"message": format!("Missing required argument: '{required}' for tool '{tool_name}'"),
"hint": Self::missing_arg_hint(&tool_name, required),
"_wm_route": route_meta.clone(),
}));
}
}
let result = match &self.pipeline {
Some(p) => p.dispatch(t.as_ref(), ctx, tool_args).await,
None => t.call(ctx, tool_args).await,
};
if let Some(ref router) = self.embedding_router {
let success = result.is_ok();
if let Some(emb) = &query_emb {
router.record_outcome_with_embedding(&tool_name, thought, success, emb);
} else {
let router = Arc::clone(router);
let tool_name_owned = tool_name.clone();
let thought_owned = thought.to_string();
tokio::task::spawn_blocking(move || {
router.record_outcome(&tool_name_owned, &thought_owned, success);
});
}
}
match result {
Ok(mut output) => {
if let Value::Object(ref mut map) = output {
let mut meta = route_meta.clone();
meta["input"] = json!(thought.chars().take(200).collect::<String>());
map.insert("_wm_route".into(), meta);
}
Ok(output)
}
Err(e) => Ok(json!({
"status": "error",
"error": e.to_string(),
"_wm_route": route_meta.clone(),
})),
}
}
None => Ok(json!({
"status": "error",
"message": format!("Unknown tool: '{tool_name}'"),
"_wm_route": route_meta.clone(),
})),
}
}
fn stats(&self) -> &ToolStats {
&self.stats
}
}
#[must_use]
pub fn required_arg_for(tool_name: &str) -> Option<&'static str> {
WmMetaTool::required_arg(tool_name)
}
fn parse_galaxy(s: &str) -> wm_core::Result<Galaxy> {
expansion::common::parse_galaxy(s)
}
fn content_admission_gate(content: &str) -> Result<(), String> {
if wm_memory::sanitize_content_for_index(content).is_some() {
Ok(())
} else {
Err("content must be non-empty printable text \
(no NUL bytes or control-character-heavy payloads)"
.into())
}
}
#[allow(clippy::too_many_arguments)]
pub fn register_all(
registry: &ToolRegistry,
store: &Arc<MemoryStore>,
search: Option<Arc<SearchEngine>>,
karma: Option<Arc<KarmaLedger>>,
dharma: &Option<Arc<DharmaGate>>,
substrate: Option<Arc<SubstrateMonitor>>,
resource_rules: &Option<Arc<ResourceRules>>,
associations: Arc<AssociationStore>,
spiral_tracker: Arc<std::sync::Mutex<wm_cognitive::SpiralTracker>>,
vector_store: Arc<std::sync::Mutex<VectorStore>>,
conversational: Option<ConversationalSearch>,
recall: Option<Arc<RecallEngine>>,
homeostatic_loop: Option<Arc<std::sync::Mutex<HomeostaticLoop>>>,
anomaly_detector: Option<Arc<std::sync::Mutex<AnomalyDetector>>>,
sensorimotor_bus: Option<Arc<std::sync::Mutex<SensorimotorBus>>>,
reflex_loop: Option<Arc<std::sync::Mutex<ReflexLoop>>>,
gan_ying_bus: Option<&Arc<std::sync::Mutex<GanYingBus>>>,
transaction_state: expansion::TransactionState,
escalation_queue: Option<&Arc<std::sync::Mutex<wm_governance::EscalationQueue>>>,
firewall: Option<&Arc<expansion::firewall::TxFirewall>>,
code_graph: Option<&Arc<std::sync::Mutex<expansion::code::CodeGraph>>>,
registry_persistence: expansion::RegistryPersistenceMode,
circuit_breakers: Arc<wm_dispatch::CircuitBreakerRegistry>,
) -> ToolRegistry {
let reg = registry
.register(Arc::new(MemoryCreateTool::new(
store.clone(),
search.clone(),
recall.clone(),
)))
.register(Arc::new(MemoryBatchCreateTool::new(
store.clone(),
search.clone(),
recall.clone(),
)))
.register(Arc::new(MemoryReadTool::new(store.clone())))
.register(Arc::new(MemoryListTool::new(store.clone())))
.register(Arc::new(MemoryDeleteTool::new(
store.clone(),
search.clone(),
)))
.register(Arc::new(MemoryBatchDeleteTool::new(
store.clone(),
search.clone(),
)))
.register(Arc::new(MemoryQueryTool::new(store.clone())))
.register(Arc::new(MemoryAssociateTool::new(store.clone())))
.register(Arc::new(MemoryAssociationsTool::new(store.clone())))
.register(Arc::new(MemoryVectorSearchTool::new(
store.clone(),
vector_store,
)))
.register(Arc::new(GnosisTool::new(store.clone())))
.register(Arc::new(expansion::MemoryReembedTool::new(recall.clone())));
let mut reg = expansion::breaker_tools::register_breakers(®, circuit_breakers);
if let Some(conv) = conversational {
reg = reg.register(Arc::new(MemoryChatTool::new(conv)));
}
if let Some(s) = search {
reg = reg.register(Arc::new(
expansion::MemoryHybridRecallTool::as_search(
store.clone(),
Some(s.clone()),
recall.clone(),
)
.with_associations(Some(associations.clone())),
));
reg = expansion::register_expansion(
®,
store,
Some(s),
recall,
associations,
spiral_tracker,
karma.clone(),
substrate.clone(),
homeostatic_loop,
anomaly_detector,
sensorimotor_bus,
reflex_loop,
gan_ying_bus,
transaction_state,
resource_rules.as_ref(),
escalation_queue,
dharma.as_ref(),
firewall,
code_graph,
registry_persistence,
);
} else {
reg = expansion::register_expansion(
®,
store,
None,
recall,
associations,
spiral_tracker,
karma.clone(),
substrate.clone(),
homeostatic_loop,
anomaly_detector,
sensorimotor_bus,
reflex_loop,
gan_ying_bus,
transaction_state,
resource_rules.as_ref(),
escalation_queue,
dharma.as_ref(),
firewall,
code_graph,
registry_persistence,
);
}
if let Some(k) = karma {
reg = reg.register(Arc::new(KarmaReportTool::new(k)));
}
if let Some(d) = dharma {
reg = reg.register(Arc::new(DharmaStatusTool::new(d.clone())));
}
if let Some(s) = substrate {
reg = reg
.register(Arc::new(HarmonyVectorTool::new(s.clone())))
.register(Arc::new(HarmonyHistoryTool::new(s.clone())));
if let Some(d) = dharma {
if let Some(r) = resource_rules {
reg = reg
.register(Arc::new(GnosisStatusTool::new(
d.clone(),
r.clone(),
s.clone(),
)))
.register(Arc::new(GnosisHistoryTool::new(s)))
.register(Arc::new(GnosisExplainTool::new(d.clone(), r.clone())));
}
}
}
reg
}
pub fn register_meta_tools(
registry: &ToolRegistry,
store: &Arc<MemoryStore>,
shadow_stats: Arc<std::sync::RwLock<embedding_router::ShadowModeStats>>,
) -> ToolRegistry {
register_meta_tools_with_router(registry, store, shadow_stats, None).0
}
#[must_use]
pub fn register_meta_tools_with_router(
registry: &ToolRegistry,
store: &Arc<MemoryStore>,
shadow_stats: Arc<std::sync::RwLock<embedding_router::ShadowModeStats>>,
pipeline: Option<Arc<DispatchPipeline>>,
) -> (ToolRegistry, Option<Arc<embedding_router::EmbeddingRouter>>) {
let base_snapshot: Vec<Arc<dyn Tool>> = registry.all();
let tool_count = base_snapshot.len();
let non_gnosis: Vec<Arc<dyn Tool>> = base_snapshot
.iter()
.filter(|t| t.name() != "gnosis")
.cloned()
.collect();
let mut list_builder = ToolRegistryBuilder::new();
for tool in &non_gnosis {
list_builder.register(tool.clone());
}
let list_registry = Arc::new(list_builder.build());
let tools_list = Arc::new(ToolsListTool::new(Arc::clone(&list_registry)));
let usage_report = Arc::new(expansion::ToolsUsageReportTool::new(list_registry));
let gnosis = Arc::new(GnosisTool::with_tool_count(Arc::clone(store), tool_count));
let mut wm_builder = ToolRegistryBuilder::new();
for tool in &non_gnosis {
wm_builder.register(tool.clone());
}
wm_builder.register(tools_list.clone());
wm_builder.register(usage_report.clone());
wm_builder.register(gnosis.clone());
let shadow_report = Arc::new(expansion::NluShadowReportTool::new(Arc::clone(
&shadow_stats,
)));
wm_builder.register(shadow_report.clone());
let wm = Arc::new(WmMetaTool::with_router_shadow_stats_and_pipeline(
Arc::new(wm_builder.build()),
wm_memory::create_embedder(),
shadow_stats,
pipeline,
));
let router = wm.embedding_router().cloned();
let mut final_builder = ToolRegistryBuilder::new();
for tool in non_gnosis {
final_builder.register(tool);
}
final_builder.register(tools_list);
final_builder.register(usage_report);
final_builder.register(wm);
final_builder.register(gnosis);
final_builder.register(shadow_report);
(final_builder.build(), router)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use wm_core::BrainWave;
fn test_store() -> Arc<MemoryStore> {
let tmp = tempfile::tempdir().unwrap();
Arc::new(MemoryStore::open_default(tmp.path()).unwrap())
}
fn cold_factors() -> wm_memory::cold_storage::OuterRimFactors {
wm_memory::cold_storage::OuterRimFactors {
age_factor: 1.0,
access_factor: 1.0,
resonance_factor: 1.0,
emotional_factor: 1.0,
importance_factor: 1.0,
distance: 1.0,
}
}
fn freeze_for_read_test(
store: &MemoryStore,
galaxy: Galaxy,
content: &str,
is_private: bool,
) -> (uuid::Uuid, wm_memory::cold_storage::ColdRecord) {
let mut memory = wm_memory::Memory::new(galaxy, content.to_string());
memory.metadata.is_private = is_private;
let id = memory.metadata.id;
store.put(galaxy, &memory).unwrap();
let record = store
.freeze_to_cold(
None,
id,
1.0,
cold_factors(),
None,
None,
wm_memory::cold_storage::CompressionCodec::Gzip,
)
.unwrap();
(id, record)
}
fn readonly_tree_snapshot(root: &Path) -> BTreeMap<PathBuf, Vec<u8>> {
fn visit(root: &Path, path: &Path, out: &mut BTreeMap<PathBuf, Vec<u8>>) {
for entry in std::fs::read_dir(path).unwrap() {
let entry = entry.unwrap();
let entry_path = entry.path();
let relative = entry_path.strip_prefix(root).unwrap().to_path_buf();
if relative == Path::new("lock.mdb") {
continue;
}
if entry.file_type().unwrap().is_dir() {
out.insert(relative.clone(), Vec::new());
visit(root, &entry_path, out);
} else {
out.insert(relative, std::fs::read(entry_path).unwrap());
}
}
}
let mut snapshot = BTreeMap::new();
visit(root, root, &mut snapshot);
snapshot
}
#[tokio::test]
async fn memory_create_rejects_empty_and_binary_content() {
let store = test_store();
let tool = MemoryCreateTool::new(store, None, None);
let mut ctx = Context::default();
for content in ["", " ", "\n\t \n"] {
let err = tool
.call(&mut ctx, json!({ "content": content }))
.await
.unwrap_err();
assert!(
err.to_string().contains("non-empty printable text"),
"blank content must be refused: {err}"
);
}
let err = tool
.call(&mut ctx, json!({"content": "ok\u{0}but binary"}))
.await
.unwrap_err();
assert!(
err.to_string().contains("non-empty printable text"),
"NUL content must be refused: {err}"
);
let err = tool
.call(
&mut ctx,
json!({"content": "\u{1}\u{2}\u{3}\u{4}\u{5}\u{6}"}),
)
.await
.unwrap_err();
assert!(
err.to_string().contains("non-empty printable text"),
"{err}"
);
let ok = tool
.call(&mut ctx, json!({"content": "a perfectly ordinary memory"}))
.await
.unwrap();
assert_eq!(ok["status"], "success", "{ok}");
}
#[tokio::test]
async fn memory_create_warns_on_credential_shaped_content() {
let store = test_store();
let tool = MemoryCreateTool::new(store, None, None);
let mut ctx = Context::default();
let clean = tool
.call(
&mut ctx,
json!({"content": "the password policy requires rotation"}),
)
.await
.unwrap();
assert!(clean.get("warnings").is_none(), "clean content: {clean}");
let flagged = tool
.call(
&mut ctx,
json!({"content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEow\n-----END RSA PRIVATE KEY-----"}),
)
.await
.unwrap();
assert_eq!(
flagged["status"], "success",
"warning, not refusal: {flagged}"
);
let warnings = flagged["warnings"].as_array().unwrap();
assert!(
warnings[0].as_str().unwrap().contains("private_key_pem"),
"got: {warnings:?}"
);
assert!(warnings[0].as_str().unwrap().contains("keyring"));
}
#[tokio::test]
async fn memory_batch_create_aggregates_credential_warnings() {
let store = test_store();
let tool = MemoryBatchCreateTool::new(store, None, None);
let mut ctx = Context::default();
let r = tool
.call(
&mut ctx,
json!({"items": [
{"content": "ordinary note"},
{"content": "AKIAIOSFODNN7EXAMPLE"},
]}),
)
.await
.unwrap();
assert_eq!(r["count"], 2);
let warnings = r["warnings"].as_array().unwrap();
assert!(warnings[0].as_str().unwrap().contains("aws_access_key_id"));
}
fn test_registry_with(store: &Arc<MemoryStore>) -> ToolRegistry {
let registry = ToolRegistry::new();
let associations = Arc::new(AssociationStore::open(store.env()).unwrap());
let spiral_tracker =
Arc::new(std::sync::Mutex::new(wm_cognitive::SpiralTracker::default()));
let vector_store = Arc::new(std::sync::Mutex::new(wm_memory::VectorStore::new()));
register_all(
®istry,
store,
None,
None,
&None,
None,
&None,
associations,
spiral_tracker,
vector_store,
None,
None,
None,
None,
None,
None,
None,
std::sync::Arc::new(std::sync::Mutex::new(None)),
None,
None,
None,
expansion::RegistryPersistenceMode::Normal,
Arc::new(wm_dispatch::CircuitBreakerRegistry::default()),
)
}
#[tokio::test]
async fn memory_create_and_read() {
let store = test_store();
let tool = MemoryCreateTool::new(store.clone(), None, None);
let mut ctx = Context::new(BrainWave::Gamma);
let args = json!({"content": "test memory content", "galaxy": "codex"});
let result = tool.call(&mut ctx, args).await.unwrap();
assert_eq!(result["status"], "success");
assert!(
result.get("warnings").is_none(),
"a clean create discloses no episodic warning: {result}"
);
let id = result["id"].as_str().unwrap();
let read_tool = MemoryReadTool::new(store.clone());
let result = read_tool.call(&mut ctx, json!({"id": id})).await.unwrap();
assert_eq!(result["status"], "success");
assert_eq!(result["content"], "test memory content");
let episodic = store
.episodic()
.get(uuid::Uuid::parse_str(id).unwrap())
.unwrap()
.expect("explicit memory writes mirror into episodic storage");
assert_eq!(episodic.content, "test memory content");
}
#[test]
fn episodic_capture_failure_is_disclosed_on_the_response() {
let mut clean = json!({"status": "success"});
attach_episodic_capture_warning(&mut clean, None);
assert!(clean.get("warnings").is_none());
let mut partial = json!({"status": "success", "warnings": ["existing"]});
attach_episodic_capture_warning(
&mut partial,
Some("MDB_BAD_VALSIZE: value size exceeds limit".into()),
);
let warnings = partial["warnings"].as_array().unwrap();
assert_eq!(warnings.len(), 2, "existing warnings preserved: {partial}");
assert!(
warnings[1]
.as_str()
.unwrap()
.contains("episodic capture failed")
);
assert!(warnings[1].as_str().unwrap().contains("MDB_BAD_VALSIZE"));
}
#[tokio::test]
async fn memory_read_recovers_cold_content_after_reopen_without_thawing() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().to_path_buf();
let content = "cold UTF-8: cafe\u{301} \u{1f980}\nsecond line — exact".repeat(128);
let (id, before) = {
let store = MemoryStore::open_default(&path).unwrap();
freeze_for_read_test(&store, Galaxy::Codex, &content, false)
};
let store = Arc::new(MemoryStore::open_default(&path).unwrap());
assert!(store.get(Galaxy::Codex, id).unwrap().is_none());
assert_eq!(store.get_cold_record(id).unwrap().as_ref(), Some(&before));
let before_read_tree = readonly_tree_snapshot(&path);
let mut ctx = Context::default();
let result = MemoryReadTool::new(store.clone())
.call(&mut ctx, json!({"id": id, "galaxy": "codex"}))
.await
.unwrap();
assert_eq!(result["status"], "success");
assert_eq!(result["content"], content);
assert!(store.get(Galaxy::Codex, id).unwrap().is_none());
assert_eq!(store.get_cold_record(id).unwrap().as_ref(), Some(&before));
assert_eq!(readonly_tree_snapshot(&path), before_read_tree);
drop(store);
let reopened = MemoryStore::open_default(&path).unwrap();
assert!(reopened.get(Galaxy::Codex, id).unwrap().is_none());
assert_eq!(
reopened.get_cold_record(id).unwrap().as_ref(),
Some(&before)
);
}
#[tokio::test]
async fn memory_read_cold_fallback_is_galaxy_bound_and_missing_is_not_found() {
let store = test_store();
let (id, _) = freeze_for_read_test(&store, Galaxy::Codex, "cold codex only", false);
let mut ctx = Context::default();
let tool = MemoryReadTool::new(store);
let wrong_galaxy = tool
.call(&mut ctx, json!({"id": id, "galaxy": "sessions"}))
.await
.unwrap();
assert_eq!(wrong_galaxy["status"], "not_found");
assert_eq!(wrong_galaxy["galaxy"], "sessions");
assert!(wrong_galaxy.get("content").is_none());
let missing = tool
.call(
&mut ctx,
json!({"id": uuid::Uuid::new_v4(), "galaxy": "codex"}),
)
.await
.unwrap();
assert_eq!(missing["status"], "not_found");
assert!(missing.get("content").is_none());
}
#[tokio::test]
async fn memory_read_private_cold_record_is_not_found_without_headers() {
let store = test_store();
let (id, _) = freeze_for_read_test(&store, Galaxy::Codex, "private cold content", true);
let mut ctx = Context::default();
let result = MemoryReadTool::new(store)
.call(&mut ctx, json!({"id": id, "galaxy": "codex"}))
.await
.unwrap();
assert_eq!(result["status"], "not_found");
assert!(result.get("content").is_none());
assert!(result.get("tags").is_none());
assert!(result.get("created_at").is_none());
}
#[tokio::test]
async fn memory_read_refuses_corrupt_cold_payload_or_header_mismatch() {
let store = test_store();
let (payload_id, mut payload_record) =
freeze_for_read_test(&store, Galaxy::Codex, "payload integrity", false);
payload_record.compressed_payload[0] ^= 0xff;
store.put_cold_record(&payload_record).unwrap();
let mut ctx = Context::default();
let tool = MemoryReadTool::new(store.clone());
assert!(
tool.call(&mut ctx, json!({"id": payload_id, "galaxy": "codex"}))
.await
.is_err()
);
assert!(store.get(Galaxy::Codex, payload_id).unwrap().is_none());
let (header_id, mut header_record) =
freeze_for_read_test(&store, Galaxy::Codex, "header integrity", false);
header_record.content_hash = "wrong-header-hash".into();
store.put_cold_record(&header_record).unwrap();
assert!(
tool.call(&mut ctx, json!({"id": header_id, "galaxy": "codex"}))
.await
.is_err()
);
assert!(store.get(Galaxy::Codex, header_id).unwrap().is_none());
}
#[tokio::test]
async fn memory_create_attestation_disclosure() {
const TEST_KEY: &str = "0bd1c44170ca3d916648a983dcdb8583d22f2da5b29fdd5ede4b38e805435577";
let mut ctx = Context::new(BrainWave::Gamma);
let store = test_store();
let tool = MemoryCreateTool::with_attestation_key(store.clone(), None, None, None);
let result = tool
.call(
&mut ctx,
json!({"content": "unattested create", "galaxy": "codex"}),
)
.await
.unwrap();
assert_eq!(result["status"], "success");
assert_eq!(result["attested"], false);
assert_eq!(result["attested_reason"], "node key unavailable");
let tool = MemoryCreateTool::with_attestation_key(
store.clone(),
None,
None,
Some("not-hex".to_string()),
);
let result = tool
.call(
&mut ctx,
json!({"content": "bad key create", "galaxy": "codex"}),
)
.await
.unwrap();
assert_eq!(result["attested"], false);
assert_eq!(result["attested_reason"], "node key invalid");
let tool = MemoryCreateTool::with_attestation_key(
store.clone(),
None,
None,
Some(TEST_KEY.to_string()),
);
let result = tool
.call(
&mut ctx,
json!({"content": "attested create", "galaxy": "codex"}),
)
.await
.unwrap();
assert_eq!(result["attested"], true);
assert!(result.get("attested_reason").is_none());
let id = uuid::Uuid::parse_str(result["id"].as_str().unwrap()).unwrap();
let report = store.verify_attestation(Galaxy::Codex, id).unwrap();
assert!(report.attested, "{:?}", report.breaks);
assert!(report.signature_valid, "{:?}", report.breaks);
assert!(report.matches_head, "{:?}", report.breaks);
assert!(report.memory_present);
assert!(report.breaks.is_empty());
let mut memory = store.get(Galaxy::Codex, id).unwrap().unwrap();
memory.content = "edited after attestation".to_string();
memory.metadata.content_hash = wm_memory::content_hash(&memory.content);
store.put(Galaxy::Codex, &memory).unwrap();
let stale = store.verify_attestation(Galaxy::Codex, id).unwrap();
assert!(stale.attested);
assert!(stale.signature_valid);
assert!(!stale.matches_head);
let scanned = store.scan_attestations().unwrap();
assert_eq!(scanned.len(), 1);
assert_eq!(scanned[0].memory_id, id.to_string());
}
#[tokio::test]
async fn memory_batch_create_attests_each_item() {
const TEST_KEY: &str = "0bd1c44170ca3d916648a983dcdb8583d22f2da5b29fdd5ede4b38e805435577";
let store = test_store();
let tool = MemoryBatchCreateTool::with_attestation_key(
store.clone(),
None,
None,
Some(TEST_KEY.to_string()),
);
let mut ctx = Context::new(BrainWave::Gamma);
let result = tool
.call(
&mut ctx,
json!({"items": [{"content": "batch one"}, {"content": "batch two"}]}),
)
.await
.unwrap();
assert_eq!(result["attested_count"], 2);
assert_eq!(store.scan_attestations().unwrap().len(), 2);
let tool = MemoryBatchCreateTool::with_attestation_key(store.clone(), None, None, None);
let result = tool
.call(&mut ctx, json!({"items": [{"content": "batch three"}]}))
.await
.unwrap();
assert_eq!(result["attested_count"], 0);
assert_eq!(result["count"], 1);
}
#[tokio::test]
async fn memory_batch_create_mirrors_into_episodic_lane() {
let store = test_store();
let tool = MemoryBatchCreateTool::new(store.clone(), None, None);
let mut ctx = Context::new(BrainWave::Gamma);
let result = tool
.call(
&mut ctx,
json!({
"items": [
{"content": "batch rust retrieval"},
{"content": "batch grocery list"}
]
}),
)
.await
.unwrap();
assert_eq!(result["status"], "success");
let ids = result["ids"].as_array().unwrap();
let first = uuid::Uuid::parse_str(ids[0].as_str().unwrap()).unwrap();
let hits = store
.episodic()
.search("rust retrieval", 10, false)
.unwrap();
assert_eq!(hits.len(), 1);
assert_eq!(hits[0].record.id, first);
}
#[tokio::test]
async fn memory_list_returns_entries() {
let store = test_store();
let create = MemoryCreateTool::new(store.clone(), None, None);
let mut ctx = Context::new(BrainWave::Gamma);
for i in 0..3 {
create
.call(&mut ctx, json!({"content": format!("item-{i}")}))
.await
.unwrap();
}
let list = MemoryListTool::new(store);
let result = list.call(&mut ctx, json!({"limit": 10})).await.unwrap();
assert_eq!(result["status"], "success");
assert_eq!(result["total"], 3);
assert_eq!(result["returned"], 3);
}
#[tokio::test]
async fn memory_list_offset_and_exclude_tags_page_visible_surface() {
let store = test_store();
let mut ctx = Context::new(BrainWave::Gamma);
for i in 0..5 {
let mut m = wm_memory::Memory::new(wm_core::Galaxy::Codex, format!("page note {i}"));
if i == 1 {
m.metadata.tags = vec!["noise".into()];
}
if i == 3 {
m.metadata.is_private = true;
}
store.put(wm_core::Galaxy::Codex, &m).unwrap();
}
let list = MemoryListTool::new(store);
let all = list
.call(
&mut ctx,
json!({"galaxy": "codex", "limit": 50, "exclude_tags": ["noise"]}),
)
.await
.unwrap();
assert_eq!(all["total"], 5, "total counts the whole galaxy");
assert_eq!(all["matched"], 3, "private + excluded are invisible");
assert_eq!(all["returned"], 3);
assert_eq!(all["offset"], 0);
let page1 = list
.call(
&mut ctx,
json!({"galaxy": "codex", "limit": 2, "offset": 0, "exclude_tags": ["noise"]}),
)
.await
.unwrap();
assert_eq!(page1["returned"], 2);
let page2 = list
.call(
&mut ctx,
json!({"galaxy": "codex", "limit": 2, "offset": 2, "exclude_tags": ["noise"]}),
)
.await
.unwrap();
assert_eq!(
page2["returned"], 1,
"matched is 3 — the tail page is short"
);
assert_eq!(page2["offset"], 2);
let ids_of = |v: &Value| -> Vec<String> {
v["memories"]
.as_array()
.unwrap()
.iter()
.filter_map(|m| m["id"].as_str().map(String::from))
.collect()
};
let (p1, p2, everything) = (ids_of(&page1), ids_of(&page2), ids_of(&all));
assert_eq!(p1.len(), 2);
let mut union = p1;
union.extend(p2);
let mut sorted_union = union.clone();
sorted_union.sort();
let mut sorted_all = everything;
sorted_all.sort();
assert_eq!(sorted_union, sorted_all, "pages must partition the surface");
}
#[tokio::test]
async fn memory_create_stamps_provenance_by_claim() {
let store = test_store();
let create = MemoryCreateTool::new(store.clone(), None, None);
let mut ctx = Context::new(BrainWave::Gamma);
let silent = create
.call(&mut ctx, json!({"content": "no claim"}))
.await
.unwrap();
assert_eq!(silent["source"], "agent");
assert!((silent["source_trust"].as_f64().unwrap() - 0.7).abs() < 1e-5);
let claimed = create
.call(
&mut ctx,
json!({"content": "user dictated this", "source": "user"}),
)
.await
.unwrap();
assert_eq!(claimed["source"], "user");
assert!((claimed["source_trust"].as_f64().unwrap() - 1.0).abs() < 1e-5);
let custom = create
.call(&mut ctx, json!({"content": "web import", "source": "web"}))
.await
.unwrap();
assert_eq!(custom["source"], "web");
assert!((custom["source_trust"].as_f64().unwrap() - 0.7).abs() < 1e-5);
let fetch = |id: &str| {
store
.get(wm_core::Galaxy::Codex, uuid::Uuid::parse_str(id).unwrap())
.expect("stored")
.expect("present")
};
assert_eq!(
fetch(silent["id"].as_str().unwrap()).metadata.source,
"agent"
);
assert_eq!(
fetch(claimed["id"].as_str().unwrap()).metadata.source,
"user"
);
}
#[tokio::test]
async fn gnosis_returns_system_info() {
let store = test_store();
let tool = GnosisTool::new(store);
let mut ctx = Context::new(BrainWave::Gamma);
let result = tool.call(&mut ctx, json!({})).await.unwrap();
assert_eq!(result["status"], "success");
assert!(result["version"].is_string());
}
#[tokio::test]
async fn memory_delete_removes_entry() {
let store = test_store();
let create = MemoryCreateTool::new(store.clone(), None, None);
let mut ctx = Context::new(BrainWave::Gamma);
let result = create
.call(&mut ctx, json!({"content": "to be deleted"}))
.await
.unwrap();
let id = result["id"].as_str().unwrap();
let delete = MemoryDeleteTool::new(store.clone(), None);
let result = delete.call(&mut ctx, json!({"id": id})).await.unwrap();
assert_eq!(result["status"], "success");
let read = MemoryReadTool::new(store);
let result = read.call(&mut ctx, json!({"id": id})).await.unwrap();
assert_eq!(result["status"], "not_found");
}
#[tokio::test]
async fn memory_delete_without_galaxy_resolves_across_memory_galaxies() {
let store = test_store();
let create = MemoryCreateTool::new(store.clone(), None, None);
let mut ctx = Context::new(BrainWave::Gamma);
let result = create
.call(
&mut ctx,
json!({"content": "session decision", "galaxy": "sessions"}),
)
.await
.unwrap();
let id = result["id"].as_str().unwrap();
let delete = MemoryDeleteTool::new(store.clone(), None);
let result = delete.call(&mut ctx, json!({"id": id})).await.unwrap();
assert_eq!(result["status"], "success");
assert!(
result["galaxies"]
.as_array()
.unwrap()
.contains(&json!("sessions"))
);
let read = MemoryReadTool::new(store.clone());
let result = read
.call(&mut ctx, json!({"id": id, "galaxy": "sessions"}))
.await
.unwrap();
assert_eq!(result["status"], "not_found");
}
#[tokio::test]
async fn memory_delete_explicit_galaxy_does_not_miss_other_galaxies() {
let store = test_store();
let create = MemoryCreateTool::new(store.clone(), None, None);
let mut ctx = Context::new(BrainWave::Gamma);
let result = create
.call(
&mut ctx,
json!({"content": "in sessions", "galaxy": "sessions"}),
)
.await
.unwrap();
let id = result["id"].as_str().unwrap();
let delete = MemoryDeleteTool::new(store.clone(), None);
let result = delete
.call(&mut ctx, json!({"id": id, "galaxy": "codex"}))
.await
.unwrap();
assert_eq!(result["status"], "not_found");
assert!(result["hint"].is_string());
let read = MemoryReadTool::new(store.clone());
let result = read
.call(&mut ctx, json!({"id": id, "galaxy": "sessions"}))
.await
.unwrap();
assert_eq!(result["status"], "success");
}
#[tokio::test]
async fn memory_query_filters_by_tags() {
let store = test_store();
let create = MemoryCreateTool::new(store.clone(), None, None);
let mut ctx = Context::new(BrainWave::Gamma);
create
.call(&mut ctx, json!({"content": "tagged", "tags": ["rust"]}))
.await
.unwrap();
create
.call(&mut ctx, json!({"content": "untagged"}))
.await
.unwrap();
let query = MemoryQueryTool::new(store);
let result = query
.call(&mut ctx, json!({"tags": ["rust"]}))
.await
.unwrap();
assert_eq!(result["status"], "success");
assert_eq!(result["total"], 1);
}
#[tokio::test]
async fn memory_query_time_range_passthrough() {
let store = test_store();
let mut ctx = Context::new(BrainWave::Gamma);
let mut old = wm_memory::Memory::new(wm_core::Galaxy::Codex, "old relic".into());
old.metadata.created_at = chrono::Utc::now() - chrono::Duration::days(60);
store.put(wm_core::Galaxy::Codex, &old).unwrap();
let mut recent = wm_memory::Memory::new(wm_core::Galaxy::Codex, "recent note".into());
recent.metadata.created_at = chrono::Utc::now() - chrono::Duration::hours(1);
store.put(wm_core::Galaxy::Codex, &recent).unwrap();
let query = MemoryQueryTool::new(store);
let cutoff = (chrono::Utc::now() - chrono::Duration::days(1))
.to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
let only_recent = query
.call(&mut ctx, json!({"created_after": cutoff}))
.await
.unwrap();
assert_eq!(only_recent["total"], 1);
assert_eq!(only_recent["memories"][0]["content_preview"], "recent note");
assert_eq!(
only_recent["time_range"]["created_after"], cutoff,
"the applied time range must be disclosed"
);
let only_old = query
.call(&mut ctx, json!({"created_before": cutoff}))
.await
.unwrap();
assert_eq!(only_old["total"], 1);
assert_eq!(only_old["memories"][0]["content_preview"], "old relic");
let both = query
.call(
&mut ctx,
json!({
"created_after": (chrono::Utc::now() - chrono::Duration::days(90)).to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
"created_before": cutoff,
}),
)
.await
.unwrap();
assert_eq!(both["total"], 1);
assert_eq!(both["memories"][0]["content_preview"], "old relic");
let bad = query
.call(&mut ctx, json!({"created_after": "not-a-timestamp"}))
.await;
assert!(bad.is_err(), "invalid RFC 3339 must be refused");
}
#[tokio::test]
async fn memory_vector_search_by_embedding() {
let store = test_store();
let vector_store = Arc::new(std::sync::Mutex::new(wm_memory::VectorStore::new()));
{
let mut vs = vector_store.lock().unwrap();
vs.add(uuid::Uuid::new_v4(), Galaxy::Codex, vec![1.0, 0.0, 0.0]);
vs.add(uuid::Uuid::new_v4(), Galaxy::Codex, vec![0.9, 0.1, 0.0]);
vs.add(uuid::Uuid::new_v4(), Galaxy::Research, vec![0.0, 1.0, 0.0]);
}
let tool = MemoryVectorSearchTool::new(store, vector_store);
let mut ctx = Context::new(BrainWave::Gamma);
let result = tool
.call(&mut ctx, json!({"embedding": [1.0, 0.0, 0.0], "limit": 2}))
.await
.unwrap();
assert_eq!(result["status"], "success");
assert_eq!(result["total"], 2);
}
#[tokio::test]
async fn memory_vector_search_missing_args() {
let store = test_store();
let vector_store = Arc::new(std::sync::Mutex::new(wm_memory::VectorStore::new()));
let tool = MemoryVectorSearchTool::new(store, vector_store);
let mut ctx = Context::new(BrainWave::Gamma);
let result = tool.call(&mut ctx, json!({"limit": 5})).await;
assert!(result.is_err());
}
#[tokio::test]
async fn wm_routes_vector_search_to_memory_vector_search() {
let store = test_store();
let registry = test_registry_with(&store);
let registry = register_meta_tools(
®istry,
&store,
std::sync::Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
);
let wm = registry.get("wm").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = wm
.call(
&mut ctx,
json!({"route": "memory.vector.search", "args": {"embedding": [1.0, 0.0, 0.0]}}),
)
.await
.unwrap();
assert_eq!(result["status"], "success");
assert_eq!(result["_wm_route"]["tool"], "memory.vector.search");
}
#[tokio::test]
async fn wm_routes_shadow_report_inside_meta_tool() {
let store = test_store();
let registry = test_registry_with(&store);
let registry = register_meta_tools(
®istry,
&store,
std::sync::Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
);
let wm = registry.get("wm").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = wm
.call(&mut ctx, json!({"route": "nlu.shadow_report"}))
.await
.unwrap();
assert_eq!(result["_wm_route"]["tool"], "nlu.shadow_report");
assert!(
result.get("total_queries").is_some(),
"expected shadow report payload"
);
}
#[tokio::test]
async fn memory_associate_and_find() {
let store = test_store();
let create = MemoryCreateTool::new(store.clone(), None, None);
let mut ctx = Context::new(BrainWave::Gamma);
let r1 = create
.call(&mut ctx, json!({"content": "source mem"}))
.await
.unwrap();
let r2 = create
.call(&mut ctx, json!({"content": "target mem"}))
.await
.unwrap();
let id1 = r1["id"].as_str().unwrap();
let id2 = r2["id"].as_str().unwrap();
let assoc = MemoryAssociateTool::new(store.clone());
let result = assoc
.call(
&mut ctx,
json!({"source": id1, "target": id2, "weight": 0.8}),
)
.await
.unwrap();
assert_eq!(result["status"], "success");
let find = MemoryAssociationsTool::new(store);
let result = find
.call(&mut ctx, json!({"id": id1, "direction": "from"}))
.await
.unwrap();
assert_eq!(result["status"], "success");
assert_eq!(result["returned"], 1);
}
#[tokio::test]
async fn karma_report_shows_entries() {
let tmp = tempfile::tempdir().unwrap();
let store = Arc::new(MemoryStore::open_default(tmp.path()).unwrap());
let ledger = Arc::new(KarmaLedger::new(store).unwrap());
ledger.record("test_tool", false, 0, true).unwrap();
ledger.record("wasteful_tool", true, 0, true).unwrap();
let tool = KarmaReportTool::new(ledger);
let mut ctx = Context::new(BrainWave::Gamma);
let result = tool.call(&mut ctx, json!({"limit": 5})).await.unwrap();
assert_eq!(result["status"], "success");
assert_eq!(result["entry_count"], 2);
assert_eq!(result["recent_entries"].as_array().unwrap().len(), 2);
}
#[tokio::test]
async fn dharma_status_returns_homeostasis() {
let gate = Arc::new(DharmaGate::default());
let tool = DharmaStatusTool::new(gate);
let mut ctx = Context::new(BrainWave::Gamma);
let result = tool.call(&mut ctx, json!({})).await.unwrap();
assert_eq!(result["status"], "success");
assert!(result["homeostasis"]["health_score"].is_f64());
assert!(result["sutras"]["ahimsa"].is_string());
assert!(result["decisions"]["total"].is_u64());
assert!(result["decisions"]["blocked_ratio"].is_number());
}
#[tokio::test]
async fn wm_routes_remember_to_memory_create() {
let store = test_store();
let registry = test_registry_with(&store);
let registry = register_meta_tools(
®istry,
&store,
std::sync::Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
);
let wm = registry.get("wm").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = wm
.call(
&mut ctx,
json!({"thought": "remember that the API uses X-User-Id headers"}),
)
.await
.unwrap();
assert_eq!(result["status"], "success");
assert_eq!(result["_wm_route"]["tool"], "memory.create");
assert!(result["id"].is_string());
}
#[tokio::test]
async fn wm_explicit_route() {
let store = test_store();
let registry = test_registry_with(&store);
let registry = register_meta_tools(
®istry,
&store,
std::sync::Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
);
let wm = registry.get("wm").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = wm
.call(
&mut ctx,
json!({
"route": "gnosis"
}),
)
.await
.unwrap();
assert_eq!(result["status"], "success");
assert_eq!(result["_wm_route"]["tool"], "gnosis");
}
#[tokio::test]
async fn wm_no_input_returns_error() {
let store = test_store();
let registry = test_registry_with(&store);
let registry = register_meta_tools(
®istry,
&store,
std::sync::Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
);
let wm = registry.get("wm").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = wm.call(&mut ctx, json!({})).await.unwrap();
assert_eq!(result["status"], "error");
}
#[tokio::test]
async fn wm_missing_route_echoes_received_keys() {
let store = test_store();
let registry = test_registry_with(&store);
let registry = register_meta_tools(
®istry,
&store,
std::sync::Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
);
let wm = registry.get("wm").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = wm
.call(
&mut ctx,
json!({"content": "x", "turn_type": "summary", "importance": 0.5}),
)
.await
.unwrap();
assert_eq!(result["status"], "error");
let message = result["message"].as_str().unwrap();
assert!(
message.contains("received argument keys"),
"error must disclose received keys, got: {message}"
);
for key in ["content", "turn_type", "importance"] {
assert!(
message.contains(key),
"error must list received key '{key}', got: {message}"
);
}
let empty = wm.call(&mut ctx, json!({})).await.unwrap();
assert!(
!empty["message"]
.as_str()
.unwrap()
.contains("received argument keys: ["),
"empty input must not list keys, got: {}",
empty["message"]
);
}
#[tokio::test]
async fn wm_unknown_tool_returns_error() {
let store = test_store();
let registry = test_registry_with(&store);
let registry = register_meta_tools(
®istry,
&store,
std::sync::Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
);
let wm = registry.get("wm").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = wm
.call(&mut ctx, json!({"route": "nonexistent.tool"}))
.await
.unwrap();
assert_eq!(result["status"], "error");
assert!(result["message"].as_str().unwrap().contains("Unknown tool"));
}
#[tokio::test]
async fn memory_query_tags_only_is_allowed() {
let store = test_store();
let mut mem = Memory::new(Galaxy::Codex, "atlas constraint note".into());
mem.metadata.tags = vec!["atlas".into(), "constraint".into()];
store.put(Galaxy::Codex, &mem).unwrap();
let registry = test_registry_with(&store);
let registry = register_meta_tools(
®istry,
&store,
std::sync::Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
);
let wm = registry.get("wm").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = wm
.call(
&mut ctx,
json!({"route": "memory.query", "args": {"tags": ["atlas", "constraint"]}}),
)
.await
.unwrap();
assert_eq!(result["status"], "success", "{result}");
assert_eq!(result["total"], 1, "{result}");
assert!(
result["memories"][0]
.to_string()
.contains("atlas constraint"),
"{result}"
);
}
#[tokio::test]
async fn memory_search_cold_discovery_is_opt_in_and_verified() {
let store = test_store();
let factors = wm_memory::cold_storage::OuterRimFactors {
age_factor: 0.5,
access_factor: 0.5,
resonance_factor: 0.5,
emotional_factor: 0.5,
importance_factor: 0.5,
distance: 0.5,
};
let mem = Memory::new(
Galaxy::Codex,
"cold original zxquniquehotcold999 deep".into(),
);
let rec = wm_memory::cold_storage::ColdRecord::new(
&mem,
0.5,
factors,
None,
None,
wm_memory::cold_storage::CompressionCodec::Gzip,
)
.unwrap();
store.put_cold_record(&rec).unwrap();
let registry = test_registry_with(&store);
let _ = ®istry;
let search = expansion::MemoryHybridRecallTool::as_search(store.clone(), None, None);
let mut ctx = Context::new(BrainWave::Gamma);
let without = search
.call(
&mut ctx,
json!({"query": "zxquniquehotcold999", "limit": 5}),
)
.await
.unwrap();
assert_eq!(without["count"], 0, "{without}");
let with = search
.call(
&mut ctx,
json!({"query": "zxquniquehotcold999", "limit": 5, "include_cold": true}),
)
.await
.unwrap();
assert_eq!(with["cold_discovery"]["no_thaw"], true, "{with}");
assert!(
with["results"]
.as_array()
.unwrap()
.iter()
.any(|r| r["source"] == "cold" && r["integrity"] == "verified"),
"{with}"
);
}
#[tokio::test]
async fn wm_missing_arg_returns_hint() {
let store = test_store();
let registry = test_registry_with(&store);
let registry = register_meta_tools(
®istry,
&store,
std::sync::Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
);
let wm = registry.get("wm").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = wm
.call(&mut ctx, json!({"route": "memory.read"}))
.await
.unwrap();
assert_eq!(result["status"], "error");
assert!(
result["message"]
.as_str()
.unwrap()
.contains("Missing required argument")
);
assert!(result["hint"].as_str().unwrap().contains("uuid"));
}
#[test]
fn search_payload_extracts_curated_intents() {
let cases = [
(
"find BETA quartz submarine in memory",
"BETA quartz submarine",
),
(
"What do you remember about BETA quartz submarine?",
"BETA quartz submarine",
),
(
"What did we decide about BETA quartz submarine?",
"BETA quartz submarine",
),
("recall BETA quartz submarine", "BETA quartz submarine"),
("look up BETA quartz submarine", "BETA quartz submarine"),
("search for rust", "rust"),
("search memory for rust", "rust"),
];
for (thought, expected) in cases {
let got = WmMetaTool::extract_payload(thought, "memory.search");
assert_eq!(
got,
Some(("query".to_string(), expected.to_string())),
"for {thought:?}"
);
}
}
#[tokio::test]
async fn wm_auto_route_missing_arg_returns_hint() {
let store = test_store();
let registry = test_registry_with(&store);
let registry = register_meta_tools(
®istry,
&store,
std::sync::Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
);
let wm = registry.get("wm").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = wm
.call(&mut ctx, json!({"thought": "fetch memory"}))
.await
.unwrap();
assert_eq!(result["status"], "error");
assert!(
result["hint"].as_str().is_some_and(|h| h.contains("uuid")),
"expected a read hint, got {result}"
);
}
#[tokio::test]
async fn wm_routes_karma_to_karma_report() {
let tmp = tempfile::tempdir().unwrap();
let store = Arc::new(MemoryStore::open_default(tmp.path()).unwrap());
let ledger = Arc::new(KarmaLedger::new(store.clone()).unwrap());
let gate = Arc::new(DharmaGate::default());
let registry = ToolRegistry::new();
let associations = Arc::new(AssociationStore::open(store.env()).unwrap());
let spiral_tracker =
Arc::new(std::sync::Mutex::new(wm_cognitive::SpiralTracker::default()));
let vector_store = Arc::new(std::sync::Mutex::new(wm_memory::VectorStore::new()));
let registry = register_all(
®istry,
&store,
None,
Some(ledger),
&Some(gate),
None,
&None,
associations,
spiral_tracker,
vector_store,
None,
None,
None,
None,
None,
None,
None,
std::sync::Arc::new(std::sync::Mutex::new(None)),
None,
None,
None,
expansion::RegistryPersistenceMode::Normal,
Arc::new(wm_dispatch::CircuitBreakerRegistry::default()),
);
let registry = register_meta_tools(
®istry,
&store,
std::sync::Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
);
let wm = registry.get("wm").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = wm
.call(&mut ctx, json!({"thought": "show me the karma report"}))
.await
.unwrap();
assert_eq!(result["status"], "success");
assert_eq!(result["_wm_route"]["tool"], "karma.report");
}
fn test_registry_with_pipeline(
store: &Arc<MemoryStore>,
) -> (ToolRegistry, Arc<DispatchPipeline>) {
let registry = test_registry_with(store);
let pipeline = Arc::new(DispatchPipeline::with_defaults());
let (registry, _router) = register_meta_tools_with_router(
®istry,
store,
std::sync::Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
Some(pipeline.clone()),
);
(registry, pipeline)
}
#[tokio::test]
async fn wm_route_destructive_without_confirm_blocked_by_pipeline() {
let store = test_store();
let (registry, _pipeline) = test_registry_with_pipeline(&store);
let wm = registry.get("wm").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = wm
.call(
&mut ctx,
json!({"route": "memory.delete", "args": {"id": "00000000-0000-0000-0000-000000000001"}}),
)
.await
.unwrap();
assert_eq!(result["status"], "error");
assert!(
result["error"].as_str().unwrap().contains("destructive"),
"expected destructive-gate message, got: {result}"
);
assert!(result["error"].as_str().unwrap().contains("confirm"));
}
#[tokio::test]
async fn wm_route_destructive_with_confirm_proceeds() {
let store = test_store();
let (registry, _pipeline) = test_registry_with_pipeline(&store);
let memory = Memory::new(Galaxy::Codex, "delete me via wm route".into());
let id = memory.metadata.id;
store.put(Galaxy::Codex, &memory).unwrap();
let wm = registry.get("wm").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = wm
.call(
&mut ctx,
json!({"route": "memory.delete", "args": {"id": id.to_string(), "galaxy": "codex", "confirm": true}}),
)
.await
.unwrap();
assert_eq!(result["status"], "success");
assert_eq!(result["_wm_route"]["tool"], "memory.delete");
assert!(store.get(Galaxy::Codex, id).unwrap().is_none());
}
#[tokio::test]
async fn wm_thought_cannot_reach_destructive_tool() {
let store = test_store();
let (registry, _pipeline) = test_registry_with_pipeline(&store);
let wm = registry.get("wm").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = wm
.call(
&mut ctx,
json!({"thought": "delete memory 00000000-0000-0000-0000-000000000001"}),
)
.await
.unwrap();
assert_eq!(result["status"], "error");
assert!(
result["message"]
.as_str()
.unwrap()
.contains("cannot be reached via natural language"),
"expected NLU hard-block message, got: {result}"
);
}
#[tokio::test]
async fn wm_thought_cannot_reach_destructive_tool_even_with_confirm() {
let store = test_store();
let (registry, _pipeline) = test_registry_with_pipeline(&store);
let wm = registry.get("wm").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = wm
.call(
&mut ctx,
json!({"thought": "delete memory 00000000-0000-0000-0000-000000000001", "args": {"confirm": true, "id": "00000000-0000-0000-0000-000000000001"}}),
)
.await
.unwrap();
assert_eq!(result["status"], "error");
assert!(
result["message"]
.as_str()
.unwrap()
.contains("cannot be reached via natural language")
);
}
#[tokio::test]
async fn nlu_cannot_reach_any_destructive_tool() {
let store = test_store();
let (registry, _pipeline) = test_registry_with_pipeline(&store);
let wm = registry.get("wm").unwrap();
let destructive_tools: Vec<String> = registry
.all_ref()
.iter()
.filter(|t| t.effects().destructive)
.map(|t| t.name().to_string())
.collect();
assert!(
!destructive_tools.is_empty(),
"registry must contain at least one destructive tool for this test to be meaningful"
);
let mut ctx = Context::new(BrainWave::Gamma);
for tool_name in &destructive_tools {
let result = wm
.call(
&mut ctx,
json!({
"thought": tool_name,
"args": {"confirm": true}
}),
)
.await
.unwrap();
let routed_tool = result
.get("_wm_route")
.and_then(|r| r.get("tool"))
.and_then(|t| t.as_str())
.unwrap_or("");
let resolved_destructive = registry
.get(routed_tool)
.is_some_and(|t| t.effects().destructive);
assert!(
result["status"] != "success" || !resolved_destructive,
"destructive tool '{tool_name}' executed via NLU (resolved as '{routed_tool}') — structural gate failed"
);
if routed_tool == tool_name {
assert!(
result
.get("message")
.and_then(|m| m.as_str())
.is_some_and(|m| m.contains("cannot be reached via natural language")),
"destructive tool '{tool_name}' was routed to but gate message missing: {result}"
);
}
let nl_phrase = match tool_name.as_str() {
"memory.delete" => "delete memory 00000000-0000-0000-0000-000000000001",
"transaction.rollback" => "rollback the transaction",
"galaxy.purge" => "purge galaxy codex",
"galaxy.transfer" => "transfer galaxy codex to archive",
"galaxy.restore" => "restore galaxy codex from snapshot",
"memory.consolidate" => "consolidate memories in codex",
"memory.deduplicate" => "deduplicate memories in codex",
"karma.purge" => "purge karma ledger",
"system.flush" => "flush low importance memories",
"galaxy.cold_rotate" => "rotate telemetry noise to cold storage",
_ => tool_name.as_str(),
};
let result2 = wm
.call(&mut ctx, json!({"thought": nl_phrase}))
.await
.unwrap();
let routed_tool2 = result2
.get("_wm_route")
.and_then(|r| r.get("tool"))
.and_then(|t| t.as_str())
.unwrap_or("");
let resolved_destructive2 = registry
.get(routed_tool2)
.is_some_and(|t| t.effects().destructive);
assert!(
result2["status"] != "success" || !resolved_destructive2,
"destructive tool '{tool_name}' executed via NLU phrase '{nl_phrase}' (resolved as '{routed_tool2}') — structural gate failed"
);
if routed_tool2 == tool_name {
assert!(
result2
.get("message")
.and_then(|m| m.as_str())
.is_some_and(|m| m.contains("cannot be reached via natural language")),
"destructive tool '{tool_name}' was routed to via '{nl_phrase}' but gate message missing: {result2}"
);
}
}
}
#[tokio::test]
async fn nlu_abstention_returns_error_for_unmatched_query() {
let store = test_store();
let (registry, _pipeline) = test_registry_with_pipeline(&store);
let wm = registry.get("wm").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = wm
.call(&mut ctx, json!({"thought": "xyzzy quux blargh frobnicate"}))
.await
.unwrap();
assert_eq!(result["status"], "error");
assert!(
result
.get("_wm_route")
.and_then(|r| r.get("abstained"))
.and_then(serde_json::Value::as_bool)
.unwrap_or(false),
"expected abstained=true, got: {result}"
);
assert!(
result["message"]
.as_str()
.unwrap()
.contains("Could not confidently match"),
"expected abstention message, got: {result}"
);
}
#[tokio::test]
async fn nlu_abstention_does_not_fire_for_explicit_route() {
let store = test_store();
let (registry, _pipeline) = test_registry_with_pipeline(&store);
let wm = registry.get("wm").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = wm.call(&mut ctx, json!({"route": "gnosis"})).await.unwrap();
assert_eq!(result["status"], "success");
assert!(
!result
.get("_wm_route")
.and_then(|r| r.get("abstained"))
.and_then(serde_json::Value::as_bool)
.unwrap_or(false),
"explicit route should not abstain, got: {result}"
);
}
struct FakeVecEmbedder;
impl wm_memory::Embedder for FakeVecEmbedder {
fn embed_batch(&self, texts: &[&str]) -> wm_core::Result<Vec<Vec<f32>>> {
Ok(texts
.iter()
.map(|t| {
let mut v = vec![0.0_f32; 16];
for (i, b) in t.bytes().take(16).enumerate() {
v[i] = f32::from(b) / 255.0;
}
v
})
.collect())
}
fn dimension(&self) -> usize {
16
}
fn is_available(&self) -> bool {
true
}
fn backend_name(&self) -> &'static str {
"fake"
}
}
#[tokio::test]
async fn wm_classify_async_routes_off_thread_with_embedding_router() {
let store = test_store();
let registry = test_registry_with(&store);
let shadow = std::sync::Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
));
let router = embedding_router::EmbeddingRouter::with_descriptions(
Box::new(FakeVecEmbedder),
embedding_router::tool_descriptions(),
)
.expect("fake-embedder router should build");
let mut meta = WmMetaTool::with_router_shadow_stats_and_pipeline(
std::sync::Arc::new(registry),
wm_memory::create_embedder(),
shadow,
None,
);
meta.embedding_router = Some(std::sync::Arc::new(router));
let (tool, conf, emb) = meta.classify_async("remember the meeting notes").await;
assert!(!tool.is_empty());
assert!(conf >= 0.0);
assert!(
emb.is_some(),
"query embedding should be returned for OATS reuse"
);
}
#[tokio::test]
async fn tools_list_shows_all() {
let store = test_store();
let registry = test_registry_with(&store);
let registry = register_meta_tools(
®istry,
&store,
std::sync::Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
);
let list = registry.get("tools.list").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = list.call(&mut ctx, json!({})).await.unwrap();
assert_eq!(result["status"], "success");
assert!(result["total"].as_u64().unwrap() >= 7);
}
#[tokio::test]
async fn tools_list_exposes_curated_argument_schemas() {
let store = test_store();
let registry = test_registry_with(&store);
let registry = register_meta_tools(
®istry,
&store,
std::sync::Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
);
let list = registry.get("tools.list").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = list.call(&mut ctx, json!({})).await.unwrap();
let tools = result["tools"].as_array().unwrap();
let create = tools
.iter()
.find(|t| t["name"] == "memory.create")
.expect("tools.list must include memory.create");
let schema = &create["input_schema"];
assert_eq!(schema["type"], "object");
assert!(
schema["properties"].get("content").is_some(),
"memory.create schema must describe content, got: {schema}"
);
assert!(
schema["required"]
.as_array()
.unwrap()
.iter()
.any(|r| r == "content"),
"memory.create schema must require content"
);
let rollback = tools
.iter()
.find(|t| t["name"] == "transaction.rollback")
.expect("tools.list must include transaction.rollback");
assert!(
rollback["input_schema"]["required"]
.as_array()
.unwrap()
.iter()
.any(|r| r == "confirm"),
"transaction.rollback schema must require confirm"
);
let annotations = &create["annotations"];
assert_eq!(annotations["readOnlyHint"], false, "memory.create writes");
assert_eq!(annotations["destructiveHint"], false);
assert_eq!(
rollback["annotations"]["destructiveHint"], true,
"transaction.rollback is destructive"
);
let list_tool = tools
.iter()
.find(|t| t["name"] == "memory.list")
.expect("tools.list must include memory.list");
assert_eq!(
list_tool["annotations"]["readOnlyHint"], true,
"memory.list is read-only"
);
}
#[tokio::test]
async fn tools_list_filters_by_brain_wave() {
let store = test_store();
let registry = test_registry_with(&store);
let registry = register_meta_tools(
®istry,
&store,
std::sync::Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
);
let list = registry.get("tools.list").unwrap();
let mut ctx_gamma = Context::new(BrainWave::Gamma);
let result_gamma = list.call(&mut ctx_gamma, json!({})).await.unwrap();
let gamma_count = result_gamma["total"].as_u64().unwrap();
assert!(gamma_count >= 7);
let mut ctx_alpha = Context::new(BrainWave::Alpha);
let result_alpha = list.call(&mut ctx_alpha, json!({})).await.unwrap();
let alpha_count = result_alpha["total"].as_u64().unwrap();
assert!(alpha_count < gamma_count);
assert!(alpha_count > 0);
let mut ctx_delta = Context::new(BrainWave::Delta);
let result_delta = list.call(&mut ctx_delta, json!({})).await.unwrap();
assert_eq!(result_delta["total"], 0);
}
#[tokio::test]
async fn gnosis_includes_brain_wave_and_tool_count() {
let store = test_store();
let registry = test_registry_with(&store);
let registry = register_meta_tools(
®istry,
&store,
std::sync::Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
);
let gnosis = registry.get("gnosis").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = gnosis.call(&mut ctx, json!({})).await.unwrap();
assert_eq!(result["status"], "success");
assert_eq!(result["brain_wave"], "Gamma");
assert!(result["available_tools"].as_u64().unwrap() >= 9);
}
#[tokio::test]
async fn gnosis_available_tools_is_total_registered() {
let store = test_store();
let registry = test_registry_with(&store);
let registry = register_meta_tools(
®istry,
&store,
std::sync::Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
);
let gnosis = registry.get("gnosis").unwrap();
let mut ctx_gamma = Context::new(BrainWave::Gamma);
let result_gamma = gnosis.call(&mut ctx_gamma, json!({})).await.unwrap();
let gamma_tools = result_gamma["available_tools"].as_u64().unwrap();
let mut ctx_delta = Context::new(BrainWave::Delta);
let result_delta = gnosis.call(&mut ctx_delta, json!({})).await.unwrap();
let delta_tools = result_delta["available_tools"].as_u64().unwrap();
assert_eq!(gamma_tools, delta_tools);
assert!(
gamma_tools >= 9,
"expected at least 9 registered tools, got {gamma_tools}"
);
}
#[tokio::test]
async fn expansion_brings_tool_count_to_50() {
let store = test_store();
let registry = test_registry_with(&store);
let registry = register_meta_tools(
®istry,
&store,
std::sync::Arc::new(std::sync::RwLock::new(
embedding_router::ShadowModeStats::default(),
)),
);
let list = registry.get("tools.list").unwrap();
let mut ctx = Context::new(BrainWave::Gamma);
let result = list.call(&mut ctx, json!({})).await.unwrap();
let total = result["total"].as_u64().unwrap();
assert!(
total >= 50,
"Expected 50+ tools after expansion, got {total}"
);
}
#[tokio::test]
async fn nlu_routes_consolidate() {
let (tool, conf) = WmMetaTool::classify("consolidate memories in codex");
assert_eq!(tool, "memory.consolidate");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_decay() {
let (tool, conf) = WmMetaTool::classify("decay old memories");
assert_eq!(tool, "memory.decay");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_batch_read() {
let (tool, conf) = WmMetaTool::classify("batch read these memories");
assert_eq!(tool, "memory.batch_read");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_update() {
let (tool, conf) = WmMetaTool::classify("update memory tags");
assert_eq!(tool, "memory.update");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_tag() {
let (tool, conf) = WmMetaTool::classify("add tag to memory");
assert_eq!(tool, "memory.tag");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_memory_stats() {
let (tool, conf) = WmMetaTool::classify("memory stats for codex");
assert_eq!(tool, "memory.stats");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_hybrid_recall() {
let (tool, conf) = WmMetaTool::classify("hybrid recall for rust");
assert_eq!(tool, "memory.hybrid_recall");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_count() {
let (tool, conf) = WmMetaTool::classify("count memories in codex");
assert_eq!(tool, "memory.count");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_tags() {
let (tool, conf) = WmMetaTool::classify("list tags in codex");
assert_eq!(tool, "memory.tags");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_associate_mine() {
let (tool, conf) = WmMetaTool::classify("mine associations in codex");
assert_eq!(tool, "memory.associate_mine");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_session_start() {
let (tool, conf) = WmMetaTool::classify("start session research");
assert_eq!(tool, "session.start");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_session_end() {
let (tool, conf) = WmMetaTool::classify("end session 12345");
assert_eq!(tool, "session.end");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_session_list() {
let (tool, conf) = WmMetaTool::classify("list sessions");
assert_eq!(tool, "session.list");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_citta_status() {
let (tool, conf) = WmMetaTool::classify("citta status");
assert_eq!(tool, "citta.status");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_citta_reflect() {
let (tool, conf) = WmMetaTool::classify("reflect on recent events");
assert_eq!(tool, "citta.reflect");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_coherence() {
let (tool, conf) = WmMetaTool::classify("check coherence");
assert_eq!(tool, "citta.coherence");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_dream_status() {
let (tool, conf) = WmMetaTool::classify("dream cycle status");
assert_eq!(tool, "dream.status");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_dream_trigger() {
let (tool, conf) = WmMetaTool::classify("trigger dream cycle");
assert_eq!(tool, "dream.trigger");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_effectiveness() {
let (tool, conf) = WmMetaTool::classify("tool effectiveness report");
assert_eq!(tool, "tools.effectiveness_report");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_retire() {
let (tool, conf) = WmMetaTool::classify("retire tool memory.old");
assert_eq!(tool, "tools.retire");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_pattern_search() {
let (tool, conf) = WmMetaTool::classify("pattern search for rust");
assert_eq!(tool, "pattern.search");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_salience() {
let (tool, conf) = WmMetaTool::classify("salience spotlight");
assert_eq!(tool, "salience.spotlight");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_serendipity() {
let (tool, conf) = WmMetaTool::classify("serendipity surface");
assert_eq!(tool, "serendipity.surface");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_constellation_detect() {
let (tool, conf) = WmMetaTool::classify("detect clusters");
assert_eq!(tool, "constellation.detect");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_constellation_list() {
let (tool, conf) = WmMetaTool::classify("list constellations");
assert_eq!(tool, "constellation.list");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_galaxy_stats() {
let (tool, conf) = WmMetaTool::classify("galaxy stats");
assert_eq!(tool, "galaxy.stats");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_galaxy_export() {
let (tool, conf) = WmMetaTool::classify("export galaxy codex");
assert_eq!(tool, "galaxy.export");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_galaxy_import() {
let (tool, conf) = WmMetaTool::classify("import galaxy codex");
assert_eq!(tool, "galaxy.import");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_karma_history() {
let (tool, conf) = WmMetaTool::classify("karma history");
assert_eq!(tool, "karma.history");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_karma_clear() {
let (tool, conf) = WmMetaTool::classify("clear karma");
assert_eq!(tool, "karma.clear");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_dharma_rules() {
let (tool, conf) = WmMetaTool::classify("dharma rules");
assert_eq!(tool, "dharma.rules");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_dharma_audit() {
let (tool, conf) = WmMetaTool::classify("dharma audit");
assert_eq!(tool, "dharma.audit");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_dharma_profiles() {
let (tool, conf) = WmMetaTool::classify("dharma profiles");
assert_eq!(tool, "dharma.profiles");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_agent_register() {
let (tool, conf) = WmMetaTool::classify("register agent worker-1");
assert_eq!(tool, "agent.register");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_agent_list() {
let (tool, conf) = WmMetaTool::classify("list agents");
assert_eq!(tool, "agent.list");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_agent_heartbeat() {
let (tool, conf) = WmMetaTool::classify("heartbeat for agent");
assert_eq!(tool, "agent.heartbeat");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_task_distribute() {
let (tool, conf) = WmMetaTool::classify("distribute task analyze data");
assert_eq!(tool, "task.distribute");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_task_status() {
let (tool, conf) = WmMetaTool::classify("task status");
assert_eq!(tool, "task.status");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_system_health() {
let (tool, conf) = WmMetaTool::classify("system health check");
assert_eq!(tool, "system.health");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_system_config() {
let (tool, conf) = WmMetaTool::classify("system config");
assert_eq!(tool, "system.config");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_system_flush() {
let (tool, conf) = WmMetaTool::classify("flush old memories");
assert_eq!(tool, "system.flush");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_memory_nearby() {
let (tool, conf) = WmMetaTool::classify("nearby memories in codex");
assert_eq!(tool, "memory.nearby");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_empty_to_gnosis() {
let (tool, conf) = WmMetaTool::classify("");
assert_eq!(tool, "gnosis");
assert_eq!(conf, 0.0);
}
#[tokio::test]
async fn nlu_routes_unknown_to_gnosis() {
let (tool, conf) = WmMetaTool::classify("xyzzy frobnicate");
assert_eq!(tool, "gnosis");
assert_eq!(conf, 0.0);
}
#[tokio::test]
async fn nlu_extract_payload_memory_search() {
let (param, value) =
WmMetaTool::extract_payload("search for rust patterns", "memory.search").unwrap();
assert_eq!(param, "query");
assert_eq!(value, "rust patterns");
}
#[tokio::test]
async fn nlu_extract_payload_session_start() {
let (param, value) =
WmMetaTool::extract_payload("start session research", "session.start").unwrap();
assert_eq!(param, "title");
assert_eq!(value, "research");
}
#[tokio::test]
async fn nlu_extract_payload_agent_register() {
let (param, value) =
WmMetaTool::extract_payload("register agent worker-1", "agent.register").unwrap();
assert_eq!(param, "name");
assert_eq!(value, "worker-1");
}
#[tokio::test]
async fn nlu_extract_payload_task_distribute() {
let (param, value) =
WmMetaTool::extract_payload("distribute task analyze data", "task.distribute").unwrap();
assert_eq!(param, "task");
assert_eq!(value, "analyze data");
}
#[tokio::test]
async fn nlu_count_unique_patterns() {
let inputs = [
"remember",
"recall",
"list memories",
"delete memory",
"search",
"query",
"associate",
"associations",
"consolidate",
"decay",
"batch read",
"update memory",
"tag memory",
"memory stats",
"hybrid recall",
"count memories",
"list tags",
"mine associations",
"start session",
"checkpoint",
"recall session",
"end session",
"list sessions",
"citta status",
"reflect",
"coherence",
"dream status",
"trigger dream",
"effectiveness",
"retire tool",
"pattern search",
"salience",
"serendipity",
"detect clusters",
"list constellations",
"galaxy stats",
"export galaxy",
"import galaxy",
"karma",
"karma history",
"clear karma",
"dharma rules",
"dharma audit",
"dharma profiles",
"dharma",
"register agent",
"list agents",
"heartbeat",
"distribute task",
"task status",
"system health",
"system config",
"flush",
"tools",
"nearby memories",
];
let mut tools: std::collections::HashSet<&str> = std::collections::HashSet::new();
for input in &inputs {
let (tool, _) = WmMetaTool::classify(input);
tools.insert(tool);
}
assert!(
tools.len() >= 30,
"Expected 30+ unique NLU targets, got {}",
tools.len()
);
}
#[tokio::test]
async fn nlu_routes_shadow_report() {
let (tool, conf) = WmMetaTool::classify("shadow mode disagreement report");
assert_eq!(tool, "nlu.shadow_report");
assert!(conf > 0.0);
}
#[tokio::test]
async fn nlu_routes_oats_report() {
let (tool, conf) = WmMetaTool::classify("oats disagreement nlu router");
assert_eq!(tool, "nlu.shadow_report");
assert!(conf > 0.0);
}
#[test]
fn glyph_roundtrip_known_codes() {
let raw = json!({"route": "memory.search", "args": {"query": "x", "limit": 3}});
let encoded = encode_glyph("memory.search", &json!({"query": "x", "limit": 3}));
assert_eq!(encoded["r"], "Ms");
assert_eq!(encoded["a"]["q"], "x");
assert_eq!(encoded["a"]["n"], 3);
let decoded = decode_glyph(&encoded).expect("glyph input must decode");
assert_eq!(decoded["route"], raw["route"]);
assert_eq!(decoded["args"]["query"], "x");
assert_eq!(decoded["args"]["limit"], 3);
}
#[test]
fn glyph_unknown_codes_pass_through() {
let weird = json!({"r": "not-a-code", "a": {"zzz": 1}});
assert!(decode_glyph(&weird).is_none(), "unknown route code refuses");
let partial = json!({"r": "Ms", "a": {"zzz": 1}});
let decoded = decode_glyph(&partial).expect("known route decodes");
assert_eq!(decoded["args"]["zzz"], 1, "unknown arg code passes through");
assert_eq!(decode_glyph(&json!({"thought": "hi"})), None);
}
#[test]
fn glyph_book_covers_measured_routes() {
for route in [
"memory.search",
"memory.create",
"session.record",
"session.continuity",
"dharma.escalate",
"graph.walk",
"tools.list",
"citta.status",
] {
assert!(
glyph_lookup(GLYPH_ROUTES, route).is_some(),
"missing {route}"
);
}
}
#[test]
fn glyph_logographic_ideograms_decode_losslessly() {
let search_call = json!({
"r": "忆",
"a": {
"问": "auth failure",
"数": 5
}
});
let decoded = decode_glyph(&search_call).expect("logographic search decodes");
assert_eq!(decoded["route"], "memory.search");
assert_eq!(decoded["args"]["query"], "auth failure");
assert_eq!(decoded["args"]["limit"], 5);
let checkpoint_call = json!({
"r": "契",
"a": {
"文": "v9.3 milestone reached"
}
});
let decoded_cp = decode_glyph(&checkpoint_call).expect("checkpoint decodes");
assert_eq!(decoded_cp["route"], "session.checkpoint");
assert_eq!(decoded_cp["args"]["content"], "v9.3 milestone reached");
let status_call = json!({"r": "心", "a": {}});
let decoded_st = decode_glyph(&status_call).expect("citta status decodes");
assert_eq!(decoded_st["route"], "citta.status");
}
#[test]
fn lkep_expression_decodes_and_normalizes() {
let (route, args) =
decode_lkep(&json!("忆(问=\"deadlock\", 数=3)")).expect("LKEP string decodes");
assert_eq!(route, "memory.search");
assert_eq!(args["query"], "deadlock");
assert_eq!(args["limit"], 3);
let (route2, args2) =
decode_lkep(&json!("忆: memory corruption")).expect("colon syntax decodes");
assert_eq!(route2, "memory.search");
assert_eq!(args2["query"], "memory corruption");
let (route3, args3) = decode_lkep(&json!("律")).expect("bare route decodes");
assert_eq!(route3, "dharma.rules");
assert_eq!(args3, json!({}));
let (route4, args4) =
decode_lkep(&json!({"忆": "fast lookup"})).expect("root ideogram decodes");
assert_eq!(route4, "memory.search");
assert_eq!(args4["query"], "fast lookup");
}
}