Skip to content

Commit f7c05be

Browse files
committed
brain: recall-quality, multi-agent isolation, and storage hardening
A single coherent pass over the read path, the extractor, multi-agent scoping, and table initialization. Pre-release; breaking changes made in place with no compat shims. storage / metadata - Eager table materialization: open_or_init_schema now materializes every catalog table on open, so read paths can trust a table exists rather than each guarding TableDoesNotExist. Removes ~50 dead guards across metadata/ops/planner/workers; a fresh shard no longer logs "entity HNSW startup rebuild: metadata scan failed". - Drop version suffixes from 6 table names (edges_v2 -> edges, etc.) and all 28 rkyv type tags (::vN). No migrations: wipe data/. retrieval - Graph lane: keep the memory-anchor walk at depth 1 (depth 2 floods fusion with entity-dense memories on a diverse corpus). - Semantic soft-timeout 50ms -> 1000ms (CPU BGE embed needs headroom); log per-call embed/search split. - Rerank: combine fused_score + alpha*normalize(rerank) instead of letting the cross-encoder logit replace the fused order, so a confident multi-retriever consensus survives a noisy single-pair logit while close ties still flip. extractor - Drop the brain:fact wildcard sink: predicates outside the active schema are now dropped (with a metric), not collapsed onto (subject, brain:fact) where they tripped spurious supersession. - system schema prompt: omit a statement when no declared predicate fits, rather than coercing to brain:fact. - Entity-name normalization: strip a leading determiner; reject over-long / pure-quantity surfaces before resolution. - Delete the now-unused original_predicate_qname field stack-wide (core, metadata row, ops payload, wire view, sdk, explore, shell). multi-agent - RECALL isolates to the calling agent by default; agent_filter scopes to an explicit set and include_other_agents opts out. Wired through QueryRequest, the wire RecallRequest, both retrievers' filters (now Vec<AgentId>), the recall handler, sdk builder, and shell flags. - MemoryResult carries agent_id so callers see per-hit provenance.
1 parent cbcfd32 commit f7c05be

109 files changed

Lines changed: 1966 additions & 1752 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/brain-core/src/nodes/statement.rs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -358,19 +358,9 @@ pub struct Statement {
358358
/// date X" without resurrecting tombstones.
359359
pub record_invalidated_at_unix_nanos: Option<u64>,
360360

361-
/// LLM-coined predicate qname when this row landed on the
362-
/// `brain:fact` wildcard sink. `None` for statements whose
363-
/// `predicate` resolves to a schema-declared row — the wildcard
364-
/// sink preserves the model's original intent so a later schema
365-
/// upload can promote `(brain:fact, original_predicate_qname)` rows
366-
/// into typed predicates without re-extraction.
367-
pub original_predicate_qname: Option<String>,
368-
369361
/// Per-statement statefulness flag. Copied from
370362
/// `PredicateDefinition.is_stateful` at write time for schema-
371-
/// declared predicates; carries the LLM's per-extraction signal
372-
/// for `brain:fact` wildcard rows (where the predicate registry
373-
/// row is generic and can't speak for the individual fact).
363+
/// declared predicates.
374364
pub is_stateful: bool,
375365
}
376366

@@ -417,7 +407,6 @@ impl Statement {
417407
tombstoned_at_unix_nanos: None,
418408
tombstone_reason: None,
419409
record_invalidated_at_unix_nanos: None,
420-
original_predicate_qname: None,
421410
is_stateful: false,
422411
}
423412
}

crates/brain-explore/src/render/memory.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::io::{self, Write};
1010
use brain_protocol::envelope::response::MemoryResult;
1111
use serde_json::{json, Value};
1212

13-
use crate::render::{fmt_hex_16, fmt_id, fmt_kind, fmt_short_id};
13+
use crate::render::{fmt_hex_16, fmt_hex_16_bare, fmt_id, fmt_kind, fmt_short_id, fmt_uuid};
1414
use crate::table::middle_truncate;
1515
use crate::theme::Token;
1616
use crate::{Render, RenderCtx};
@@ -72,6 +72,14 @@ impl Render for RecallResults {
7272
score_painted,
7373
format!("sem={:.4}", r.similarity_score),
7474
];
75+
// Owning agent — first 8 hex of the agent uuid. Only shown
76+
// when known (non-zero); on a cross-agent recall this is
77+
// how the reader tells whose memory each hit is. The zero
78+
// agent is the anonymous owner and adds only noise.
79+
if r.agent_id != [0u8; 16] {
80+
let short_agent: String = fmt_hex_16_bare(&r.agent_id).chars().take(8).collect();
81+
meta.push(format!("agent={short_agent}"));
82+
}
7583
// Cross-encoder relevance, only present when rerank scored
7684
// this hit. Surfacing it makes the re-sort auditable.
7785
if let Some(rr) = r.rerank_score {
@@ -232,6 +240,7 @@ impl Render for RecallResults {
232240
"lsn": r.lsn,
233241
"flags": r.flags,
234242
"kind": fmt_kind(r.kind),
243+
"agent_id": fmt_uuid(&r.agent_id),
235244
"context_id": r.context_id,
236245
"created_at_unix_nanos": r.created_at_unix_nanos,
237246
"last_accessed_at_unix_nanos": r.last_accessed_at_unix_nanos,
@@ -289,6 +298,7 @@ mod tests {
289298
confidence: score,
290299
salience: 0.5,
291300
kind: MemoryKindWire::Episodic,
301+
agent_id: [0u8; 16],
292302
context_id: 0,
293303
created_at_unix_nanos: 0,
294304
last_accessed_at_unix_nanos: 0,
@@ -359,6 +369,25 @@ mod tests {
359369
assert!(s.contains("rr=7.2500"), "rerank column: {s}");
360370
}
361371

372+
#[test]
373+
fn shows_agent_token_only_when_known() {
374+
// Zero agent (anonymous) → no agent= token.
375+
let r = RecallResults(vec![make_hit("anon", 0.9)]);
376+
let mut buf = Vec::new();
377+
r.render_table(&ctx(), &mut buf).unwrap();
378+
let s = String::from_utf8(buf).unwrap();
379+
assert!(!s.contains("agent="), "zero agent should not render token: {s}");
380+
381+
// Known agent → first 8 hex of the uuid rides on the line.
382+
let mut hit = make_hit("owned", 0.9);
383+
hit.agent_id = [0xab, 0xcd, 0xef, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
384+
let r = RecallResults(vec![hit]);
385+
let mut buf = Vec::new();
386+
r.render_table(&ctx(), &mut buf).unwrap();
387+
let s = String::from_utf8(buf).unwrap();
388+
assert!(s.contains("agent=abcdef01"), "expected agent token: {s}");
389+
}
390+
362391
#[test]
363392
fn flags_clustered_scores() {
364393
// Two hits with identical scores → footer warns about ranking.

crates/brain-explore/src/render/statement_card.rs

Lines changed: 2 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,6 @@ pub struct StatementCard {
4545
pub object: ObjectRef,
4646
pub confidence: f32,
4747
pub evidence_memories: Vec<MemorySummary>,
48-
/// Set when the statement landed on the `brain:fact` wildcard
49-
/// sink: the renderer displays this in place of `predicate_qname`
50-
/// and appends an "(auto-coined)" hint so the user knows the
51-
/// predicate isn't in the schema yet.
52-
pub original_predicate_qname: Option<String>,
5348
/// Record-time invalidation. When set, the substrate no longer
5449
/// believes this statement (superseded / tombstoned / FORGET
5550
/// cascade). The renderer surfaces this with a warning line so the
@@ -65,26 +60,14 @@ impl Render for StatementCard {
6560
let body_width = policy.width.saturating_sub(2);
6661

6762
let subject = theme.paint(Token::Accent, &self.subject_canonical, policy);
68-
// Auto-coined rows surface the LLM's original predicate name
69-
// instead of the literal `brain:fact` so the user sees the
70-
// intent; the suffix tells them the predicate isn't declared
71-
// in the schema yet (a declare-this hint).
72-
let (predicate_label, auto_coined) = match &self.original_predicate_qname {
73-
Some(orig) => (orig.clone(), true),
74-
None => (self.predicate_qname.clone(), false),
75-
};
63+
let predicate_label = self.predicate_qname.clone();
7664
let predicate = theme.paint(Token::Predicate, &predicate_label, policy);
7765
let object = match &self.object {
7866
ObjectRef::Entity { name, .. } => theme.paint(Token::EntityId, name, policy),
7967
ObjectRef::Literal(s) => theme.paint(Token::Value, s, policy),
8068
};
8169
let kind = theme.paint(Token::Muted, &self.kind, policy);
82-
if auto_coined {
83-
let hint = theme.paint(Token::Muted, "(auto-coined)", policy);
84-
writeln!(w, "[{kind}] {subject} {predicate} {object} {hint}")?;
85-
} else {
86-
writeln!(w, "[{kind}] {subject} {predicate} {object}")?;
87-
}
70+
writeln!(w, "[{kind}] {subject} {predicate} {object}")?;
8871

8972
let id_painted = theme.paint(Token::StatementId, &self.id, policy);
9073
let conf_str = format!("{:.2}", self.confidence);
@@ -128,8 +111,6 @@ impl Render for StatementCard {
128111
"statement_kind": self.kind,
129112
"subject_canonical": self.subject_canonical,
130113
"predicate": self.predicate_qname,
131-
"original_predicate_qname": self.original_predicate_qname,
132-
"auto_coined": self.original_predicate_qname.is_some(),
133114
"object": object,
134115
"confidence": self.confidence,
135116
"evidence_memories": self.evidence_memories.iter().map(|m| json!({
@@ -178,7 +159,6 @@ mod tests {
178159
short_id: "s2/m17/v1".into(),
179160
text: "Priya works at Acme Corp".into(),
180161
}],
181-
original_predicate_qname: None,
182162
record_invalidated_at_unix_nanos: None,
183163
}
184164
}
@@ -215,30 +195,6 @@ mod tests {
215195
assert_eq!(v["object"]["value"], "Senior Engineer");
216196
}
217197

218-
#[test]
219-
fn auto_coined_predicate_shows_original_and_hint() {
220-
let mut c = sample();
221-
c.predicate_qname = "brain:fact".into();
222-
c.original_predicate_qname = Some("works_at".into());
223-
c.object = ObjectRef::Literal("billing rewrite".into());
224-
225-
let mut buf = Vec::new();
226-
c.render_table(&ctx(), &mut buf).unwrap();
227-
let s = String::from_utf8(buf).unwrap();
228-
// Renderer shows the LLM's qname, not the literal sink name.
229-
assert!(s.contains("works_at"));
230-
assert!(!s.contains("brain:fact"));
231-
assert!(s.contains("(auto-coined)"));
232-
}
233-
234-
#[test]
235-
fn declared_predicate_omits_auto_coined_hint() {
236-
let mut buf = Vec::new();
237-
sample().render_table(&ctx(), &mut buf).unwrap();
238-
let s = String::from_utf8(buf).unwrap();
239-
assert!(!s.contains("(auto-coined)"));
240-
}
241-
242198
#[test]
243199
fn record_invalidated_renders_warning_line() {
244200
let mut c = sample();
@@ -274,17 +230,4 @@ mod tests {
274230
let v2 = sample().render_json(&ctx());
275231
assert_eq!(v2["record_invalidated"], false);
276232
}
277-
278-
#[test]
279-
fn json_carries_auto_coined_metadata() {
280-
let mut c = sample();
281-
c.predicate_qname = "brain:fact".into();
282-
c.original_predicate_qname = Some("works_at".into());
283-
let v = c.render_json(&ctx());
284-
assert_eq!(v["original_predicate_qname"], "works_at");
285-
assert_eq!(v["auto_coined"], true);
286-
287-
let v2 = sample().render_json(&ctx());
288-
assert_eq!(v2["auto_coined"], false);
289-
}
290233
}

0 commit comments

Comments
 (0)