use crate::models::field_names;
use serde_json::{Value, json};
use crate::llm::OllamaClient;
use crate::models::{GovernancePolicy, Memory};
use crate::{db, hnsw::VectorIndex};
use super::AUTONOMY_MIN_CONTENT_LEN;
pub(super) fn autonomy_skip_reason(
autonomous_hooks: bool,
llm_present: bool,
content_len: usize,
namespace: &str,
) -> Option<&'static str> {
if !autonomous_hooks {
Some("disabled")
} else if !llm_present {
Some("no_llm")
} else if content_len < AUTONOMY_MIN_CONTENT_LEN {
Some("content_too_short")
} else if namespace.starts_with('_') {
Some("internal_namespace")
} else {
None
}
}
pub(super) struct AutonomyHookOutcome {
pub auto_tags: Vec<String>,
pub confirmed_contradictions: Vec<String>,
}
pub(super) fn maybe_run_autonomy_hooks(
conn: &rusqlite::Connection,
llm: &OllamaClient,
mem: &Memory,
actual_id: &str,
existing: &[Memory],
ns_policy: &GovernancePolicy,
) -> AutonomyHookOutcome {
let mut auto_tags: Vec<String> = Vec::new();
let mut confirmed_contradictions: Vec<String> = Vec::new();
match llm.auto_tag(&mem.title, &mem.content, None) {
Ok(tags) => {
auto_tags = tags.into_iter().take(8).collect();
}
Err(e) => {
tracing::warn!("auto_tag hook failed for {}: {}", actual_id, e);
}
}
if ns_policy.effective_legacy_per_pair_classifier() {
for cand in existing {
if cand.id == actual_id || cand.id == mem.id {
continue;
}
match llm.detect_contradiction(&mem.content, &cand.content) {
Ok(true) => confirmed_contradictions.push(cand.id.clone()),
Ok(false) => {}
Err(e) => {
tracing::warn!(
"detect_contradiction hook failed ({actual_id} vs {}): {e}",
cand.id
);
}
}
}
}
if !auto_tags.is_empty() || !confirmed_contradictions.is_empty() {
let mut updated_metadata = mem.metadata.clone();
if let Some(obj) = updated_metadata.as_object_mut() {
if !auto_tags.is_empty() {
obj.insert("auto_tags".to_string(), json!(auto_tags));
}
if !confirmed_contradictions.is_empty() {
obj.insert(
field_names::CONFIRMED_CONTRADICTIONS.to_string(),
json!(confirmed_contradictions),
);
}
}
if let Err(e) = db::update(
conn,
actual_id,
None,
None,
None,
None,
None,
None,
None,
None,
Some(&updated_metadata),
) {
tracing::warn!(
"autonomy-hook metadata update failed for {}: {}",
actual_id,
e
);
}
}
AutonomyHookOutcome {
auto_tags,
confirmed_contradictions,
}
}
pub(super) fn merge_autonomy_outcome_into_response(
response: &mut Value,
outcome: &AutonomyHookOutcome,
) {
if !outcome.auto_tags.is_empty() {
response["auto_tags"] = json!(outcome.auto_tags);
}
if !outcome.confirmed_contradictions.is_empty() {
response[field_names::CONFIRMED_CONTRADICTIONS] = json!(outcome.confirmed_contradictions);
}
}
#[allow(dead_code)]
pub(super) type Idx = VectorIndex;