knowledge: per-persona reviewed-only mode + agent reads include reviewed

Two coupled changes to the agent's recall surface:

1. Default scope expanded. recall_facts_for_photo and recall_entities
   used to filter to status='active' only — which silently dropped
   'reviewed' (human-verified) facts. Now they surface active +
   reviewed by default. Reviewed is strictly more trusted than
   active and shouldn't have been hidden. Rejected and superseded
   stay filtered.

2. New persona toggle `reviewed_only_facts` (BOOLEAN, default false,
   migration 2026-05-10-000400). When set, the agent's recall on
   that persona returns ONLY facts with status='reviewed' — strict
   mode for tasks where hallucinated agent claims are particularly
   costly. Wired:
   - schema.rs / Persona / InsertPersona / PersonaPatch grow the
     field.
   - PersonaView returns it as `reviewedOnlyFacts` (camelCase wire).
   - PUT /personas/{id} accepts it (mobile editor surfaces it).
   - InsightGenerator now carries a PersonaDao reference so
     recall_facts_for_photo can read the active persona's flag at
     start; one extra read per recall, cheap.

Composes with include_all_memories: that operates on the persona
*scope* axis (single vs hive), reviewed_only_facts on the *status*
axis. They're orthogonal.

Legacy persona rows pick up the default false on migration; no
behavior change unless explicitly toggled. The 4 existing persona
construction sites (one production, two tests, one InsertPersona in
knowledge_dao tests) all default the field. populate_knowledge bin
+ state.rs constructors also wire the new persona_dao arg.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cameron Cordes
2026-05-10 20:21:39 -04:00
parent f53338923d
commit 86c331571d
10 changed files with 110 additions and 4 deletions

View File

@@ -1420,6 +1420,7 @@ mod tests {
include_all_memories: false,
created_at: 0,
updated_at: 0,
reviewed_only_facts: false,
})
.execute(c.deref_mut())
.unwrap();

View File

@@ -324,6 +324,10 @@ pub struct InsertPersona<'a> {
pub include_all_memories: bool,
pub created_at: i64,
pub updated_at: i64,
/// "Strict mode" — agent reads only see facts with status =
/// 'reviewed' (human-verified). Default false. See migration
/// 2026-05-10-000400.
pub reviewed_only_facts: bool,
}
#[derive(Serialize, Queryable, Clone, Debug)]
@@ -337,6 +341,7 @@ pub struct Persona {
pub include_all_memories: bool,
pub created_at: i64,
pub updated_at: i64,
pub reviewed_only_facts: bool,
}
#[derive(Insertable)]

View File

@@ -17,6 +17,7 @@ pub struct PersonaPatch {
pub name: Option<String>,
pub system_prompt: Option<String>,
pub include_all_memories: Option<bool>,
pub reviewed_only_facts: Option<bool>,
}
/// One row of a bulk migration upload. Fields named to match the JSON
@@ -164,6 +165,7 @@ impl PersonaDao for SqlitePersonaDao {
include_all_memories: include_all,
created_at: now,
updated_at: now,
reviewed_only_facts: false,
})
.execute(conn.deref_mut())
.map_err(|e| anyhow::anyhow!("Insert error: {}", e))?;
@@ -211,6 +213,15 @@ impl PersonaDao for SqlitePersonaDao {
.execute(conn.deref_mut())
.map_err(|e| anyhow::anyhow!("Update include_all error: {}", e))?;
}
if let Some(new_reviewed_only) = patch.reviewed_only_facts {
diesel::update(personas.filter(user_id.eq(uid)).filter(persona_id.eq(pid)))
.set((
reviewed_only_facts.eq(new_reviewed_only),
updated_at.eq(now),
))
.execute(conn.deref_mut())
.map_err(|e| anyhow::anyhow!("Update reviewed_only_facts error: {}", e))?;
}
personas
.filter(user_id.eq(uid))
@@ -384,6 +395,7 @@ mod tests {
name: Some("Renamed".into()),
system_prompt: Some("new prompt".into()),
include_all_memories: None,
reviewed_only_facts: None,
},
)
.unwrap()
@@ -412,6 +424,7 @@ mod tests {
name: None,
system_prompt: None,
include_all_memories: Some(true),
reviewed_only_facts: None,
},
)
.unwrap()

View File

@@ -177,6 +177,7 @@ diesel::table! {
include_all_memories -> Bool,
created_at -> BigInt,
updated_at -> BigInt,
reviewed_only_facts -> Bool,
}
}