remem-ai 0.6.71

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use anyhow::Result;

use super::*;

fn capture_codex_transcript_message(
    conn: &rusqlite::Connection,
    session_id: &str,
    role: &str,
    content: &str,
) -> Result<i64> {
    let outcome = db::record_captured_event(
        conn,
        &db::CaptureEventInput {
            host: "codex-cli",
            session_id,
            project: "/tmp/remem",
            cwd: None,
            event_type: "message",
            role: Some(role),
            tool_name: Some("codex-transcript"),
            content,
            task_kind: Some(db::ExtractionTaskKind::SessionRollup),
        },
    )?;
    Ok(outcome.event_row_id)
}

#[tokio::test]
async fn session_rollup_promotes_summary_claim_from_trusted_codex_user_event() -> Result<()> {
    let mut conn = setup_conn();
    let session_id = "sess-rollup-message-evidence";
    let request = "Fix summary evidence binding";
    let decision = "Transcript messages are captured as immutable evidence for summary promotion.";
    let candidate_text = format!("[Context: {request}]\n\n{decision}");
    let message_id = capture_codex_transcript_message(&conn, session_id, "user", &candidate_text)?;
    capture(
        &conn,
        session_id,
        "session_stop",
        &serde_json::json!({
            "session_id": session_id,
            "cwd": "/tmp/remem"
        })
        .to_string(),
    )?;
    let stop_id: i64 = conn.query_row(
        "SELECT id FROM captured_events
         WHERE session_id = ?1 AND event_type = 'session_stop'",
        [session_id],
        |row| row.get(0),
    )?;
    let task = claim_rollup_task(&mut conn)?;

    let result = process_with_summarizer(&mut conn, &task, |_prompt| async move {
        Ok(xml_response_with_structured_fields(
            "Promote summary claims from captured Codex message evidence.",
            request,
            decision,
            "",
            "",
            "",
            "",
        ))
    })
    .await?;
    assert_eq!(result, SessionRollupResult::Written);

    let (review_status, evidence_json, source_trust): (String, String, String) = conn.query_row(
        "SELECT review_status, evidence_event_ids, source_trust_class
         FROM memory_candidates
         WHERE text = ?1",
        [&candidate_text],
        |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
    )?;
    assert_eq!(review_status, "auto_promoted");
    assert_eq!(
        serde_json::from_str::<Vec<i64>>(&evidence_json)?,
        vec![message_id]
    );
    assert!(!serde_json::from_str::<Vec<i64>>(&evidence_json)?.contains(&stop_id));
    assert_eq!(source_trust, "user_prompt");

    let memory_count: i64 = conn.query_row(
        "SELECT COUNT(*) FROM memories WHERE content = ?1",
        [&candidate_text],
        |row| row.get(0),
    )?;
    assert_eq!(memory_count, 1);
    Ok(())
}

#[tokio::test]
async fn session_rollup_keeps_codex_assistant_claim_review_gated() -> Result<()> {
    let mut conn = setup_conn();
    let session_id = "sess-rollup-assistant-external";
    let request = "Keep transcript provenance intact";
    let decision = "An assistant repeated an externally sourced claim.";
    let candidate_text = format!("[Context: {request}]\n\n{decision}");
    let message_id =
        capture_codex_transcript_message(&conn, session_id, "assistant", &candidate_text)?;
    capture(
        &conn,
        session_id,
        "session_stop",
        &serde_json::json!({
            "session_id": session_id,
            "cwd": "/tmp/remem"
        })
        .to_string(),
    )?;
    let task = claim_rollup_task(&mut conn)?;

    let result = process_with_summarizer(&mut conn, &task, |_prompt| async move {
        Ok(xml_response_with_structured_fields(
            "Preserve assistant transcript provenance.",
            request,
            decision,
            "",
            "",
            "",
            "",
        ))
    })
    .await?;
    assert_eq!(result, SessionRollupResult::Written);

    let (review_status, block_reason, evidence_json, source_trust): (
        String,
        String,
        String,
        String,
    ) = conn.query_row(
        "SELECT review_status, auto_promote_block_reason, evidence_event_ids,
                source_trust_class
         FROM memory_candidates
         WHERE text = ?1",
        [&candidate_text],
        |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
    )?;
    assert_eq!(review_status, "pending_review");
    assert_eq!(block_reason, "source_trust_below_floor");
    assert_eq!(
        serde_json::from_str::<Vec<i64>>(&evidence_json)?,
        vec![message_id]
    );
    assert_eq!(source_trust, "external_content");

    let memory_count: i64 = conn.query_row(
        "SELECT COUNT(*) FROM memories WHERE content = ?1",
        [&candidate_text],
        |row| row.get(0),
    )?;
    assert_eq!(memory_count, 0);
    Ok(())
}