memory: origin replaces learned_in and direct (eureka-pipeline MemoryOrigin, huginn-mind memory_origin alias and facts)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a8184ac44a
commit
a746374708
4 changed files with 80 additions and 28 deletions
|
|
@ -615,6 +615,11 @@ fn memory_is_well_formed(memory: &PipelineMemory, at: &str) -> Result<(), Pipeli
|
|||
if memory.text.0.trim().is_empty() {
|
||||
return Err(format_error(&format!("{at}.text"), "an empty memory"));
|
||||
}
|
||||
if let MemoryOrigin::Direct { with, .. } = &memory.origin {
|
||||
if with.is_empty() {
|
||||
return Err(format_error(&format!("{at}.origin.with"), "a direct memory that names no one she heard it from"));
|
||||
}
|
||||
}
|
||||
if memory.salience_permille > SALIENCE_MAX_PERMILLE {
|
||||
return Err(format_error(&format!("{at}.salience_permille"), &memory.salience_permille.to_string()));
|
||||
}
|
||||
|
|
@ -778,15 +783,14 @@ value_types! {
|
|||
/// One thing a Mind keeps for herself, in her own mind: what she was asked,
|
||||
/// decided, committed to, learned or did, an idea, or the rapport between
|
||||
/// her and another Mind or person. `text` is her own words. `about` names
|
||||
/// the Matrix users it concerns (Minds included), `learned_in` where she
|
||||
/// learned it (a room alias, a DM's room id, or `self run <label>`),
|
||||
/// `direct` whether it came in a direct message, and `sources` the event
|
||||
/// ids, commits or documents it rests on; the organ fills the last three,
|
||||
/// the Matrix users it concerns (Minds included) and nothing about where it
|
||||
/// came from; `origin` is where and from whom she learned it, and `sources`
|
||||
/// the event ids, commits or documents it rests on; the organ fills both,
|
||||
/// not the model. `salience_permille` ranks it and decides nothing.
|
||||
/// `consolidates` names the memories a consolidating memory replaces.
|
||||
/// Instance-rooted: the key is `<instance>:memory:<label>`.
|
||||
pub struct PipelineMemory {
|
||||
instance: Slug, label: Label, sort: MemorySort, text: Line, about: Vec<Short>[8], learned_in: Short, direct: bool,
|
||||
instance: Slug, label: Label, sort: MemorySort, text: Line, about: Vec<Short>[8], origin: MemoryOrigin,
|
||||
sources: Vec<Short>[8], salience_permille: u16, consolidates: Vec<PipelineRef>[16],
|
||||
} => memory_is_well_formed
|
||||
}
|
||||
|
|
@ -827,6 +831,47 @@ impl PipelineRef {
|
|||
}
|
||||
}
|
||||
|
||||
/// Where a memory came from and whom she heard it from. The organ derives it
|
||||
/// from the batch of the turn that wrote the memory; the model never writes it.
|
||||
/// `about` names whom a memory concerns, this names the room and the voices.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub enum MemoryOrigin {
|
||||
/// A turn in rooms: their aliases comma-joined, and the batch's other senders.
|
||||
Room {
|
||||
rooms: Short,
|
||||
#[schemars(extend("maxItems" = 8))]
|
||||
heard_from: Vec<Short>,
|
||||
},
|
||||
/// A direct message: the DM's room id, and the others who spoke in it (whom
|
||||
/// she heard it from in confidence).
|
||||
Direct {
|
||||
room: Short,
|
||||
#[schemars(extend("maxItems" = 8))]
|
||||
with: Vec<Short>,
|
||||
},
|
||||
/// A wake: no one spoke.
|
||||
Wake,
|
||||
/// A Self run of hers, by its label.
|
||||
SelfRun { run: Short },
|
||||
}
|
||||
|
||||
impl Bounded for MemoryOrigin {
|
||||
fn validate(&self, field: &str) -> Result<(), PipelineRefusal> {
|
||||
match self {
|
||||
Self::Room { rooms, heard_from } => {
|
||||
rooms.validate(&format!("{field}.rooms"))?;
|
||||
list(&format!("{field}.heard_from"), heard_from, 8)
|
||||
}
|
||||
Self::Direct { room, with } => {
|
||||
room.validate(&format!("{field}.room"))?;
|
||||
list(&format!("{field}.with"), with, 8)
|
||||
}
|
||||
Self::Wake => Ok(()),
|
||||
Self::SelfRun { run } => run.validate(&format!("{field}.run")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// How a subject was resolved. Every referent is a parsed `PipelineRef`, so a
|
||||
/// resolution names its records by ids of the kinds they declare, validated
|
||||
/// where every other referent is; a `Fixed` commit is a `Sha` whose referent
|
||||
|
|
@ -1462,7 +1507,7 @@ mod tests {
|
|||
(D::Memory(PipelineMemory {
|
||||
instance: slug(INSTANCE), label: l("2026-10-31t041200-t0-m0"), sort: MemorySort::Committed,
|
||||
text: "I take the background shaders; Libby holds the sounds.".into(), about: vec![s("@libby:chat")],
|
||||
learned_in: s("swarm"), direct: false, sources: vec![s("$e1")], salience_permille: 700, consolidates: vec![],
|
||||
origin: MemoryOrigin::Room { rooms: s("swarm"), heard_from: vec![s("@libby:chat")] }, sources: vec![s("$e1")], salience_permille: 700, consolidates: vec![],
|
||||
}), format!("{INSTANCE}:memory:2026-10-31t041200-t0-m0")),
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use chrono::{DateTime, TimeZone, Utc};
|
|||
use cultcache_rs::CultCacheEnvelope;
|
||||
use eureka_pipeline::{
|
||||
AuthorityMap, ClaimOutcome, CodeLocation, CommitRange, CutVerification, Date, DocRef, Evidence, EvidenceKind,
|
||||
FindingConfidence, FindingOrigin, FindingSeverity, Label, Line, MemorySort, MutationRecord, OrgRepo, PipelineCampaign,
|
||||
FindingConfidence, FindingOrigin, FindingSeverity, Label, Line, MemoryOrigin, MemorySort, MutationRecord, OrgRepo, PipelineCampaign,
|
||||
PipelineCutReport, PipelineCutSpec, PipelineDocument, PipelineFinding, PipelineFollowUp, PipelineHandOff,
|
||||
PipelineInstance, PipelineKind, PipelineMemory, PipelineQuestion, PipelineRef, PipelineResolution, PipelineRuling,
|
||||
PipelineRun, PipelineStewardship, PipelineTarget, PipelineVerdict, Promise, QuestionOption, ReportCommit,
|
||||
|
|
@ -362,8 +362,7 @@ pub(crate) fn memory(label: &str, sort: MemorySort, text: &str, about: &[&str])
|
|||
sort,
|
||||
text: text.into(),
|
||||
about: about.iter().map(|who| s(who)).collect(),
|
||||
learned_in: s("swarm"),
|
||||
direct: false,
|
||||
origin: MemoryOrigin::Room { rooms: s("swarm"), heard_from: vec![] },
|
||||
sources: vec![],
|
||||
salience_permille: 500,
|
||||
consolidates: vec![],
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ use cultnet_rs::{
|
|||
Cursor, EdgeAnchor, EdgeMatch, Evaluation, LIMIT_MAX, LIMIT_MIN, PROJECTION_DOCUMENT, Row, Selection, select, validate,
|
||||
};
|
||||
use eureka_pipeline::{
|
||||
ClaimOutcome, CommitRange, Date, FindingConfidence, FindingOrigin, FindingSeverity, Label, Line, MemorySort, OrgRepo,
|
||||
ClaimOutcome, CommitRange, Date, FindingConfidence, FindingOrigin, FindingSeverity, Label, Line, MemoryOrigin, MemorySort, OrgRepo,
|
||||
PipelineDocument, PipelineKind, PipelineRef, PipelineResolution, ResolutionOutcome, RulingAuthority, RunOperator, RunTurn, Sha,
|
||||
Short, Slug, Title, pipeline_key,
|
||||
};
|
||||
|
|
@ -142,7 +142,7 @@ pub enum PipelineFacts {
|
|||
HandOff { from_instance: Slug, to_instance: Slug, repo: OrgRepo, handed_on: Date },
|
||||
Run { label: Label, operated_by: RunOperator, turn: RunTurn, started_on: Date, claims: Vec<PipelineRef> },
|
||||
Pulse { label: Label, tempo_vote_beat_s: Option<u32>, #[serde(default)] tempo_vote_withdrawn: bool, urgency_permille: Option<u16> },
|
||||
Memory { label: Label, sort: MemorySort, about: Vec<Short>, direct: bool, learned_in: Short, salience_permille: u16 },
|
||||
Memory { label: Label, sort: MemorySort, about: Vec<Short>, origin: MemoryOrigin, salience_permille: u16 },
|
||||
}
|
||||
|
||||
impl PipelineFacts {
|
||||
|
|
@ -251,8 +251,7 @@ impl PipelineFacts {
|
|||
label: value.label.clone(),
|
||||
sort: value.sort,
|
||||
about: value.about.clone(),
|
||||
direct: value.direct,
|
||||
learned_in: value.learned_in.clone(),
|
||||
origin: value.origin.clone(),
|
||||
salience_permille: value.salience_permille,
|
||||
},
|
||||
}
|
||||
|
|
@ -758,7 +757,7 @@ mod tests {
|
|||
use crate::store::test_stores::MemoryStore;
|
||||
use chrono::{TimeZone, Utc};
|
||||
use eureka_pipeline::{
|
||||
ClaimOutcome, FindingConfidence, MemorySort, PipelineDocument as D, PipelineKind as K, PipelineRefusal, ResolutionOutcome,
|
||||
ClaimOutcome, FindingConfidence, MemoryOrigin, MemorySort, PipelineDocument as D, PipelineKind as K, PipelineRefusal, ResolutionOutcome,
|
||||
};
|
||||
|
||||
fn view(mind: &Mind<MemoryStore>, kind: K, key: &str) -> PipelineDocumentView {
|
||||
|
|
@ -1496,13 +1495,13 @@ mod tests {
|
|||
}
|
||||
|
||||
/// A memory is selected by its sort, by each user it is about (a memory about two users answers
|
||||
/// for both) and by whether it was learned in a direct message; a value outside the domain is
|
||||
/// for both) and by the kind of place it was learned in; a value outside the domain is
|
||||
/// refused, and its header carries the triage facts but no text.
|
||||
#[test]
|
||||
fn memories_are_selected_by_sort_about_and_direct() {
|
||||
fn memories_are_selected_by_sort_about_and_origin() {
|
||||
let mut mind = seeded();
|
||||
let D::Memory(mut direct) = memory("dm", MemorySort::Asked, "Prioritise the fork.", &["@op:chat"]) else { panic!() };
|
||||
direct.direct = true;
|
||||
direct.origin = MemoryOrigin::Direct { room: s("!dm:chat"), with: vec![s("@op:chat")] };
|
||||
committed(admit(&mut mind, vec![
|
||||
D::Memory(direct),
|
||||
memory("plan", MemorySort::Committed, "I take the shaders.", &["@aqua:chat", "@libby:chat"]),
|
||||
|
|
@ -1518,8 +1517,10 @@ mod tests {
|
|||
assert_eq!(asking("sort", &["Committed", "Rapport"]), vec![key("bond"), key("plan")]);
|
||||
assert_eq!(asking("about", &["@libby:chat"]), vec![key("bond"), key("plan")]);
|
||||
assert_eq!(asking("about", &["@aqua:chat", "@op:chat"]), vec![key("dm"), key("plan")]);
|
||||
assert_eq!(asking("direct", &["true"]), vec![key("dm")]);
|
||||
assert_eq!(asking("direct", &["false"]), vec![key("bond"), key("plan")]);
|
||||
assert_eq!(asking("memory_origin", &["Direct"]), vec![key("dm")]);
|
||||
assert_eq!(asking("memory_origin", &["Wake", "SelfRun"]), Vec::<String>::new());
|
||||
assert_eq!(asking("memory_origin", &["Room"]), vec![key("bond"), key("plan")]);
|
||||
assert!(invalid("fields[0].values", "true")(&refused(&mind, &with(of_kinds(&[K::Memory]), any_of("memory_origin", &["true"])))));
|
||||
for sort in MemorySort::NAMES {
|
||||
assert!(mind.query(&with(of_kinds(&[K::Memory]), any_of("sort", &[sort]))).is_ok(), "{sort}");
|
||||
}
|
||||
|
|
@ -1527,7 +1528,7 @@ mod tests {
|
|||
assert!(invalid("fields[0].values", "")(&refused(&mind, &with(of_kinds(&[K::Memory]), any_of("about", &[""])))));
|
||||
let page = mind.query(&with(of_kinds(&[K::Memory]), any_of("sort", &["Rapport"]))).unwrap();
|
||||
let PipelinePageItems::Headers(found) = page.items else { panic!("headers") };
|
||||
assert!(matches!(&found[0].facts, PipelineFacts::Memory { sort: MemorySort::Rapport, direct: false, salience_permille: 500, .. }), "{:?}", found[0].facts);
|
||||
assert!(matches!(&found[0].facts, PipelineFacts::Memory { sort: MemorySort::Rapport, origin: MemoryOrigin::Room { .. }, salience_permille: 500, .. }), "{:?}", found[0].facts);
|
||||
}
|
||||
|
||||
/// A withdrawal pulse admitted into the mind is read back as a withdrawal: its
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use std::collections::BTreeMap;
|
|||
|
||||
use cultnet_rs::{Row, RowSet, Selection};
|
||||
use eureka_pipeline::{
|
||||
FindingConfidence, FindingOrigin, FindingSeverity, Label, MemorySort, OrgRepo, PipelineDocument, PipelineKind, PipelineRef, ResolutionOutcome,
|
||||
FindingConfidence, FindingOrigin, FindingSeverity, Label, MemoryOrigin, MemorySort, OrgRepo, PipelineDocument, PipelineKind, PipelineRef, ResolutionOutcome,
|
||||
RulingAuthority, RunOperator, RunTurn, Slug,
|
||||
};
|
||||
|
||||
|
|
@ -56,13 +56,15 @@ pub(crate) enum Alias {
|
|||
Sort,
|
||||
/// The Matrix users a memory is about, one value each.
|
||||
About,
|
||||
/// `true` or `false` on a memory: whether it was learned in a direct message.
|
||||
Direct,
|
||||
/// A memory's origin kind: `Room`, `Direct`, `Wake` or `SelfRun`. Named `memory_origin` because
|
||||
/// `origin` already selects a finding's origin.
|
||||
MemoryOrigin,
|
||||
}
|
||||
|
||||
const FACULTIES: [&str; 7] = ["SelfFaculty", "Imagination", "Hands", "Soul", "Life", "Eyes", "Operator"];
|
||||
const OUTCOMES: [&str; 6] = ["Superseded", "Answered", "Fixed", "Deferred", "Recorded", "Withdrawn"];
|
||||
const BOOLEANS: [&str; 2] = ["true", "false"];
|
||||
const MEMORY_ORIGINS: [&str; 4] = ["Room", "Direct", "Wake", "SelfRun"];
|
||||
/// The one value a presence alias answers with; a row without it answers nothing.
|
||||
const PRESENT: [&str; 1] = ["true"];
|
||||
|
||||
|
|
@ -86,7 +88,7 @@ impl Alias {
|
|||
Self::Urged,
|
||||
Self::Sort,
|
||||
Self::About,
|
||||
Self::Direct,
|
||||
Self::MemoryOrigin,
|
||||
];
|
||||
|
||||
fn name(self) -> &'static str {
|
||||
|
|
@ -109,7 +111,7 @@ impl Alias {
|
|||
Self::Urged => "urged",
|
||||
Self::Sort => "sort",
|
||||
Self::About => "about",
|
||||
Self::Direct => "direct",
|
||||
Self::MemoryOrigin => "memory_origin",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -131,7 +133,7 @@ impl Alias {
|
|||
Self::Outcome => kind == K::Resolution,
|
||||
Self::Turn | Self::OperatedBy | Self::Ran => kind == K::Run,
|
||||
Self::Voted | Self::Urged => kind == K::Pulse,
|
||||
Self::Sort | Self::About | Self::Direct => kind == K::Memory,
|
||||
Self::Sort | Self::About | Self::MemoryOrigin => kind == K::Memory,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -150,7 +152,7 @@ impl Alias {
|
|||
Self::OperatedBy => Some(RunOperator::NAMES),
|
||||
Self::Ran | Self::Voted | Self::Urged => Some(&PRESENT),
|
||||
Self::Sort => Some(MemorySort::NAMES),
|
||||
Self::Direct => Some(&BOOLEANS),
|
||||
Self::MemoryOrigin => Some(&MEMORY_ORIGINS),
|
||||
Self::Root | Self::Repo | Self::Cut | Self::About => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -266,7 +268,12 @@ impl SelectionRow {
|
|||
D::Memory(memory) => {
|
||||
put(Alias::Sort, vec![format!("{:?}", memory.sort)]);
|
||||
put(Alias::About, memory.about.iter().map(|who| who.0.clone()).collect());
|
||||
put(Alias::Direct, vec![memory.direct.to_string()]);
|
||||
put(Alias::MemoryOrigin, vec![match memory.origin {
|
||||
MemoryOrigin::Room { .. } => "Room",
|
||||
MemoryOrigin::Direct { .. } => "Direct",
|
||||
MemoryOrigin::Wake => "Wake",
|
||||
MemoryOrigin::SelfRun { .. } => "SelfRun",
|
||||
}.to_string()]);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue