use crate::errors::AppError;
use crate::output::{self, RememberResponse};
use crate::paths::AppPaths;
use crate::storage::chunks as storage_chunks;
use crate::storage::memories::NewMemory;
use crate::storage::urls as storage_urls;
use rusqlite::Connection;
use std::time::Instant;
#[allow(clippy::too_many_arguments)]
pub(crate) fn emit_remember_result(
conn: &Connection,
paths: &AppPaths,
args: &super::args::RememberArgs,
graph: &super::graph_input::GraphInput,
new_memory: &NewMemory,
memory_id: i64,
action: String,
version: i64,
namespace: String,
normalized_name: String,
original_name: String,
name_was_normalized: bool,
entities_persisted: usize,
relationships_persisted: usize,
relationships_updated: bool,
chunks_created: usize,
mut warnings: Vec<String>,
extracted_urls: Vec<crate::extraction::ExtractedUrl>,
extraction_method: Option<String>,
backend_invoked_passage: Option<&'static str>,
inicio: Instant,
) -> Result<(), AppError> {
let chunks_persisted = storage_chunks::count_for_memory(conn, memory_id)?;
if !new_memory.body.trim().is_empty() {
let has_vec: bool = conn
.query_row(
"SELECT EXISTS(SELECT 1 FROM memory_embeddings WHERE memory_id = ?1)",
rusqlite::params![memory_id],
|r| r.get::<_, i64>(0).map(|v| v > 0),
)
.unwrap_or(false);
if !has_vec {
tracing::warn!(target: "remember",
memory_id,
name = %normalized_name,
"memory persisted without an embedding vector; recall will be degraded until re-embedded"
);
warnings.push(
"memory persisted without an embedding vector; run `enrich --operation re-embed` to make it searchable"
.to_string(),
);
}
}
if action == "updated" {
crate::commands::enrich::cleanup_queue_entry(&paths.db, memory_id, &normalized_name);
}
let urls_persisted = if !extracted_urls.is_empty() {
let url_entries: Vec<storage_urls::MemoryUrl> = extracted_urls
.into_iter()
.map(|u| storage_urls::MemoryUrl {
url: u.url,
offset: Some(u.start as i64),
})
.collect();
storage_urls::insert_urls(conn, memory_id, &url_entries)
} else {
0
};
conn.execute_batch("PRAGMA wal_checkpoint(TRUNCATE);")?;
let created_at_epoch = chrono::Utc::now().timestamp();
let created_at_iso = crate::tz::format_iso(chrono::Utc::now());
let entities_created: Vec<String> = graph.entities.iter().map(|e| e.name.clone()).collect();
let mut enrich_recommended: Vec<String> = Vec::new();
if entities_persisted > 0 {
enrich_recommended.push("entity-descriptions".to_string());
}
if args.enqueue_enrich && !entities_created.is_empty() {
match crate::commands::enrich::enqueue_priority_entity_descriptions(
paths,
&namespace,
&entities_created,
) {
Ok(n) => {
tracing::info!(
target: "remember",
enqueued = n,
"priority entity-descriptions enqueued"
);
}
Err(e) => {
warnings.push(format!(
"enqueue_enrich failed (entities still listed in entities_created): {e}"
));
}
}
}
output::emit_json(&RememberResponse {
memory_id,
name: normalized_name.clone(),
namespace,
action: action.clone(),
operation: action,
version,
entities_persisted,
relationships_persisted,
relationships_truncated: relationships_updated,
chunks_created,
chunks_persisted,
urls_persisted,
extraction_method,
merged_into_memory_id: None,
warnings,
created_at: created_at_epoch,
created_at_iso,
elapsed_ms: inicio.elapsed().as_millis() as u64,
name_was_normalized,
original_name: name_was_normalized.then_some(original_name),
backend_invoked: backend_invoked_passage,
entities_created,
enrich_recommended,
})?;
Ok(())
}