eureka-pipeline, huginn-mind: a Sleep turn shares the Persona slot, a consolidation is a pair

RunTurn gains Sleep and RunSlot/RunTurn::slot (PersonaTurn and Sleep hold the Persona slot, a Self run the other); live_holder, launch holders and the unit template compare slots, not turns. MemoryOrigin gains Sleep { run }. Admission refuses an unpaired consolidation both ways (MindRefusal::UnpairedConsolidation): a memory that consolidates others without a Superseded resolution of each by it in the batch, and a memory Superseded by memories that do not name it. Published schemas regenerated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-11-08 11:01:39 +01:00
commit 79edda37f6
15 changed files with 293 additions and 29 deletions

View file

@ -736,10 +736,29 @@ unit_enums! {
RulingAuthority { Operator, Standing, Defaulted, Mind }
FindingOrigin { Introduced, PreExisting }
RunOperator { Operator, Mind }
RunTurn { PersonaTurn, SelfRun }
RunTurn { PersonaTurn, SelfRun, Sleep }
MemorySort { Asked, Decided, Committed, Learned, Did, Idea, Rapport }
}
/// The slots of a Mind's runs: one live run of hers per instance and slot. A
/// Persona session and her Sleep run share the Persona slot (unit
/// `mind-persona@`); a Self run holds the other.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RunSlot {
Persona,
SelfRun,
}
impl RunTurn {
/// The slot this turn holds while live; admission and launch both read it here.
pub fn slot(self) -> RunSlot {
match self {
Self::PersonaTurn | Self::Sleep => RunSlot::Persona,
Self::SelfRun => RunSlot::SelfRun,
}
}
}
value_types! {
pub struct CodeLocation { path: Short, line: u32, end_line: Option<u32>, symbol: Option<Symbol> = absent }
pub struct CommitRange { base: Sha, head: Sha }
@ -1004,6 +1023,8 @@ pub enum MemoryOrigin {
Wake,
/// A Self run of hers, by its label.
SelfRun { run: Short },
/// Her Sleep run, by its label: a memory she formed in her night.
Sleep { run: Short },
}
impl Bounded for MemoryOrigin {
@ -1018,7 +1039,7 @@ impl Bounded for MemoryOrigin {
list(&format!("{field}.with"), with, 8)
}
Self::Wake => Ok(()),
Self::SelfRun { run } => run.validate(&format!("{field}.run")),
Self::SelfRun { run } | Self::Sleep { run } => run.validate(&format!("{field}.run")),
}
}
}

View file

@ -31,7 +31,7 @@ use std::collections::BTreeSet;
use chrono::{DateTime, Utc};
use cultcache_rs::{CultCache, CultCacheEnvelope};
use eureka_pipeline::{
ClaimOutcome, FindingConfidence, Line, PipelineDocument, PipelineKind, PipelineRef, PipelineRefusal,
ClaimOutcome, FindingConfidence, Line, PipelineDocument, PipelineKind, PipelineMemory, PipelineRef, PipelineRefusal,
PipelineResolution, PipelineRun, PipelineStewardship, ResolutionOutcome, RulingAuthority, Short, Slug, pipeline_key,
validate_pipeline_write_envelope,
};
@ -626,7 +626,8 @@ fn check(docs: &Docs<'_>, staged: &Staged, mind: &Slug) -> Result<(), MindRefusa
let replay = docs.in_image(K::Run, key) == Some(&staged.document);
run_rule(docs, key, run, replay)
}
D::FollowUp(_) | D::Instance(_) | D::Pulse(_) | D::Reply(_) | D::Thought(_) | D::Memory(_) | D::Research(_) | D::Idea(_) => Ok(()),
D::Memory(memory) => consolidation_rule(docs, key, memory),
D::FollowUp(_) | D::Instance(_) | D::Pulse(_) | D::Reply(_) | D::Thought(_) | D::Research(_) | D::Idea(_) => Ok(()),
}
}
@ -648,6 +649,26 @@ fn run_rule(docs: &Docs<'_>, run_key: &str, run: &PipelineRun, replay: bool) ->
Ok(())
}
/// A consolidation is a pair: each memory it names is Superseded by it, in
/// this batch. `consolidates` is a citation, never standing; the resolution
/// is what takes the consumed memory out of force, so a consolidation whose
/// resolution is missing would leave both standing. The other direction is
/// `resolution_rule`'s: a memory is Superseded only by memories that name it.
fn consolidation_rule(docs: &Docs<'_>, key: &str, memory: &PipelineMemory) -> Result<(), MindRefusal> {
for consumed in &memory.consolidates {
let paired = docs.batch.iter().any(|staged| {
matches!(&staged.document, PipelineDocument::Resolution(resolution)
if resolution.subject == *consumed
&& matches!(&resolution.outcome, ResolutionOutcome::Superseded { by }
if by.iter().any(|supersessor| supersessor.kind == PipelineKind::Memory && supersessor.id.0 == key)))
});
if !paired {
return Err(MindRefusal::UnpairedConsolidation { memory: key.into(), consumed: consumed.id.0.clone() });
}
}
Ok(())
}
/// Revision 1 stands alone; revision N needs a resolution in the batch that
/// supersedes revision N-1 by this document. The predecessor's key is the
/// leaf's, from the same document one revision back.
@ -827,6 +848,13 @@ fn resolution_rule(docs: &Docs<'_>, resolution: &PipelineResolution) -> Result<(
{
return Err(incompatible());
}
// A memory leaves force by consolidation only: the memory that
// supersedes it must name it (`consolidation_rule` is the other half).
if subject_kind == K::Memory
&& !matches!(docs.find(K::Memory, &supersessor.id.0), Some(D::Memory(next)) if next.consolidates.contains(&resolution.subject))
{
return Err(MindRefusal::UnpairedConsolidation { memory: supersessor.id.0.clone(), consumed: subject_id.into() });
}
}
}
ResolutionOutcome::Answered { by } => {
@ -2598,10 +2626,10 @@ mod tests {
ResolutionOutcome::Deferred { to: follow() },
ResolutionOutcome::Recorded { reason: "done".into() },
withdrawn(),
superseded(&[r(K::Memory, &memory_key("m2"))]),
] {
committed(admit(&mut open(), vec![resolution(subject(), outcome)]));
}
committed(admit(&mut open(), consolidation("m3", &["m1"])));
let refused = [
(ResolutionOutcome::Deferred { to: r(K::CutSpec, &id("cut_spec", "cut-1.r1")) }, "Deferred"),
(ResolutionOutcome::Fixed { commit: sha(), by: None }, "Fixed"),
@ -2633,7 +2661,62 @@ mod tests {
refusal(admit(&mut mind, vec![consolidating(r(K::Memory, &elsewhere))])),
MindRefusal::MissingReference { kind: K::Memory, id: elsewhere }
);
committed(admit(&mut mind, vec![consolidating(r(K::Memory, &memory_key("m1")))]));
let mut paired = vec![consolidating(r(K::Memory, &memory_key("m1")))];
paired.push(resolution(r(K::Memory, &memory_key("m1")), superseded(&[r(K::Memory, &memory_key("m2"))])));
committed(admit(&mut mind, paired));
}
/// A consolidation is a pair: the memory that names a consumed memory and the Superseded
/// resolution of that memory by it come in one batch, and neither half stands alone.
#[test]
fn a_consolidation_is_paired_both_ways() {
let key = |label: &str| memory_key(label);
let m = |label: &str| r(K::Memory, &key(label));
let open = || {
let mut mind = with_specs();
committed(admit(&mut mind, vec![memory("m1", MemorySort::Idea, "One.", &[]), memory("m2", MemorySort::Idea, "Two.", &[])]));
mind
};
let unpaired = |consumed: &str| MindRefusal::UnpairedConsolidation { memory: key("m3"), consumed: key(consumed) };
// The consolidating memory without the resolution of what it consumes.
let mut lone = consolidation("m3", &["m1"]);
lone.pop();
assert_eq!(refusal(admit(&mut open(), lone)), unpaired("m1"));
// One of two consumed memories left standing.
let mut half = consolidation("m3", &["m1", "m2"]);
half.pop();
assert_eq!(refusal(admit(&mut open(), half)), unpaired("m2"));
// A resolution naming a consolidation that does not name the memory back.
let D::Memory(mut stranger) = memory("m3", MemorySort::Idea, "Not a consolidation.", &[]) else { unreachable!() };
stranger.consolidates = vec![];
let refused = refusal(admit(&mut open(), vec![D::Memory(stranger), resolution(m("m1"), superseded(&[m("m3")]))]));
assert_eq!(refused, MindRefusal::UnpairedConsolidation { memory: key("m3"), consumed: key("m1") });
// A resolution by a consolidation of other memories is no pairing for this one.
let mut other = consolidation("m3", &["m2"]);
other.push(resolution(m("m1"), superseded(&[m("m3")])));
assert_eq!(refusal(admit(&mut open(), other)), unpaired("m1"));
// The pair commits: the consumed memories out of force, the consolidation in.
let mut mind = open();
committed(admit(&mut mind, consolidation("m3", &["m1", "m2"])));
// A consumed memory already resolved cannot be consumed again.
let mut again = consolidation("m4", &["m1"]);
again.pop();
again.push(resolution_n(m("m1"), 2, superseded(&[m("m4")])));
assert_eq!(refusal(admit(&mut mind, again)), MindRefusal::AlreadyResolved { subject: key("m1") });
}
/// A Sleep run and a Persona run are one slot of hers; a Self run is the other.
#[test]
fn a_sleep_run_and_a_persona_run_share_one_slot() {
let live = |label: &str| MindRefusal::AlreadyLive { run: crate::refusal::RunId::held(&format!("{INSTANCE}:run:{label}")) };
let mut mind = with_specs();
committed(admit(&mut mind, vec![mind_run("p", RunTurn::PersonaTurn, &[])]));
assert_eq!(refusal(admit(&mut mind, vec![mind_run("s", RunTurn::Sleep, &[])])), live("p"), "a night while she is awake");
committed(admit(&mut mind, vec![mind_run("self", RunTurn::SelfRun, &[])]));
committed(admit(&mut mind, vec![recorded("p")]));
committed(admit(&mut mind, vec![mind_run("s", RunTurn::Sleep, &[])]));
assert_eq!(refusal(admit(&mut mind, vec![mind_run("p2", RunTurn::PersonaTurn, &[])])), live("s"), "a session while she sleeps");
assert_eq!(refusal(admit(&mut mind, vec![mind_run("s2", RunTurn::Sleep, &[])])), live("s"), "one night at a time");
}
fn research_key(pass: &str, label: &str) -> String {

View file

@ -255,8 +255,9 @@ impl<'i> Docs<'i> {
}
/// The in-force run of hers, other than `run_key`, that holds the slot of
/// the turn `run` is of: ruling one-live-self-run's one live run per
/// instance and turn, asked by a run's opening and by the withdrawal that
/// the turn `run` is of (a Persona session and a Sleep run share the
/// Persona slot): ruling one-live-self-run's one live run per
/// instance and slot, asked by a run's opening and by the withdrawal that
/// would put a closed run back in force. An operator's run neither holds
/// the slot nor is refused for it.
pub(crate) fn live_holder(&self, run_key: &str, run: &PipelineRun) -> Option<&str> {
@ -267,7 +268,7 @@ impl<'i> Docs<'i> {
PipelineDocument::Run(other)
if other_key != run_key
&& other.instance == run.instance
&& other.turn == run.turn
&& other.turn.slot() == run.turn.slot()
&& other.operated_by == RunOperator::Mind
&& self.in_force(PipelineKind::Run, other_key) =>
{

View file

@ -397,6 +397,17 @@ pub(crate) fn memory(label: &str, sort: MemorySort, text: &str, about: &[&str])
})
}
/// A consolidation as it is admitted: the memory `label` consolidating the memories `consumed`, and a
/// Superseded resolution of each by it. The pair is one batch.
pub(crate) fn consolidation(label: &str, consumed: &[&str]) -> Vec<PipelineDocument> {
let key = |label: &str| PipelineRef { kind: PipelineKind::Memory, id: Short(format!("{INSTANCE}:memory:{label}")) };
let PipelineDocument::Memory(mut memory) = memory(label, MemorySort::Idea, "Consolidated.", &[]) else { unreachable!() };
memory.consolidates = consumed.iter().map(|consumed| key(consumed)).collect();
std::iter::once(PipelineDocument::Memory(memory))
.chain(consumed.iter().map(|consumed| resolution(key(consumed), superseded(&[key(label)]))))
.collect()
}
/// One research note of pass `pass`: a claim standing on one observed source, serving nothing.
pub(crate) fn research(pass: &str, label: &str, claim: &str) -> PipelineDocument {
PipelineDocument::Research(eureka_pipeline::PipelineResearch {

View file

@ -1795,9 +1795,7 @@ mod tests {
committed(admit(&mut mind, vec![crate::fixtures::thought("t1", "A drift.", &[r(K::Finding, &f1), r(K::Question, &q1)])]));
let (older, newer) = (format!("{INSTANCE}:memory:m1"), format!("{INSTANCE}:memory:m2"));
committed(admit(&mut mind, vec![memory("m1", MemorySort::Idea, "one", &[])]));
let D::Memory(mut consolidating) = memory("m2", MemorySort::Idea, "two", &[]) else { unreachable!() };
consolidating.consolidates = vec![r(K::Memory, &older)];
committed(admit(&mut mind, vec![D::Memory(consolidating)]));
committed(admit(&mut mind, crate::fixtures::consolidation("m2", &["m1"])));
let (note, way) = (id("research", "p.a"), id("idea", "p.i"));
let D::Research(mut note_document) = research("p", "a", "A fact.") else { unreachable!() };
note_document.serves = vec![r(K::Question, &q1)];
@ -1816,6 +1814,7 @@ mod tests {
id("resolution", &format!("finding.{}.n1", f2.rsplit(':').next().unwrap())),
);
let spec_closed = id("resolution", "cut_spec.cut-1.r1.n1");
let older_closed = format!("{INSTANCE}:resolution:memory.m1.n1");
let spec_r2 = id("cut_spec", "cut-1.r2");
let r1 = id("ruling", "R1");
let expected: Vec<(CitationRole, &str, &str)> = vec![
@ -1835,7 +1834,9 @@ mod tests {
(CitationRole::Subject, &f1_closed, &f1),
(CitationRole::Subject, &f2_closed, &f2),
(CitationRole::Subject, &spec_closed, &spec),
(CitationRole::Subject, &older_closed, &older),
(CitationRole::SupersededBy, &spec_closed, &spec_r2),
(CitationRole::SupersededBy, &older_closed, &newer),
(CitationRole::ResolvedBy, &n1, &r1),
(CitationRole::ResolvedBy, &f1_closed, &report),
(CitationRole::DeferredTo, &f2_closed, &fu),

View file

@ -36,6 +36,8 @@ pub enum MindRefusal {
CitesResolvedDocument { kind: PipelineKind, id: String },
EmptySupersession,
UnknownSupersessor { id: String },
/// A consolidation and the supersession of the memory it consumes come together: `memory` consolidates `consumed`, or is named as superseding it, without the other half in the batch.
UnpairedConsolidation { memory: String, consumed: String },
RevisionWithoutSupersession { kind: PipelineKind, revision: u32 },
DuplicateLabel { field: String, label: String },
InvalidOptions { question: String },

View file

@ -66,7 +66,7 @@ pub(crate) enum Alias {
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"];
const MEMORY_ORIGINS: [&str; 5] = ["Room", "Direct", "Wake", "SelfRun", "Sleep"];
/// The one value a presence alias answers with; a row without it answers nothing.
const PRESENT: [&str; 1] = ["true"];
@ -288,6 +288,7 @@ impl SelectionRow {
MemoryOrigin::Direct { .. } => "Direct",
MemoryOrigin::Wake => "Wake",
MemoryOrigin::SelfRun { .. } => "SelfRun",
MemoryOrigin::Sleep { .. } => "Sleep",
}.to_string()]);
}
D::Research(research) => put(Alias::Pass, vec![research.pass.0.clone()]),

View file

@ -25,7 +25,7 @@ use chrono::{DateTime, Duration, Utc};
use cultnet_rs::Selection;
use eureka_pipeline::{
Date, Label, PipelineDocument, PipelineKind, PipelineQuestion, PipelineRef, PipelineResolution, PipelineRun, QuestionOption, ResolutionOutcome,
RunOperator, RunTurn, Slug, pipeline_key,
RunOperator, RunSlot, RunTurn, Slug, pipeline_key,
};
use huginn_mind::{
MindRefusal, PipelineAdmissionOutcome, PipelineDocumentSummary, PipelineFacts,
@ -115,12 +115,12 @@ impl Default for SystemdLauncher {
}
}
/// The unit template of one turn, named only through `unit()`, which start,
/// The unit template of one slot, named only through `unit()`, which start,
/// alive and stop all call.
fn template(turn: RunTurn) -> &'static str {
match turn {
RunTurn::PersonaTurn => "mind-persona",
RunTurn::SelfRun => "mind-self",
match turn.slot() {
RunSlot::Persona => "mind-persona",
RunSlot::SelfRun => "mind-self",
}
}
@ -340,7 +340,7 @@ pub(crate) fn holders(mind: &dyn MindPort, instance: &Slug, turn: RunTurn) -> Re
let runs = headers(mind, &in_force(any_of(of_kind(PipelineKind::Run), "root", &[&instance.0])))?;
Ok(runs
.into_iter()
.filter(|run| matches!(run.facts, PipelineFacts::Run { turn: held, operated_by: RunOperator::Mind, .. } if held == turn))
.filter(|run| matches!(run.facts, PipelineFacts::Run { turn: held, operated_by: RunOperator::Mind, .. } if held.slot() == turn.slot()))
.collect())
}
@ -384,7 +384,7 @@ pub fn open_and_launch(ports: &Ports, request: LaunchRequest) -> Result<LaunchOu
if grant.instance() != &instance {
return Ok(LaunchOutcome::Refused(Declined::GrantForOtherInstance));
}
if turn == RunTurn::PersonaTurn && !claims.is_empty() {
if turn != RunTurn::SelfRun && !claims.is_empty() {
return Ok(LaunchOutcome::Refused(Declined::PersonaRunTakesNoClaims));
}
let now = ports.clock.now();
@ -631,6 +631,32 @@ mod tests {
assert_eq!(*rig.launcher.started.borrow(), vec![(Slug(INSTANCE.into()), RunTurn::SelfRun)]);
}
/// A Sleep run and a Persona session are one slot of hers: one unit, one live holder, in either order.
#[test]
fn a_sleep_run_and_a_persona_run_share_one_slot() {
let rig = Rig::new();
let LaunchOutcome::Launched { run } = rig.launch(RunTurn::Sleep, &[]) else { panic!("not launched") };
rig.at(1);
assert_eq!(rig.launch(RunTurn::PersonaTurn, &[]), LaunchOutcome::Busy { run: run.clone() }, "a live Sleep holds the Persona slot");
assert_eq!(rig.launch(RunTurn::Sleep, &[]), LaunchOutcome::Busy { run: run.clone() });
assert!(matches!(rig.launch(RunTurn::SelfRun, &[]), LaunchOutcome::Launched { .. }), "a Self run is the other slot");
let ports = Ports { mind: &rig.mind, launcher: &rig.launcher, clock: &rig.clock, host: "yggdrasil-host" };
assert!(slot(&ports, rig.mind.instance(), RunTurn::PersonaTurn, rig.clock.now()).unwrap().live.is_some());
assert_eq!(unit(&eureka(), RunTurn::Sleep).unwrap(), "mind-persona@eureka.service", "the night runs in the Persona unit");
rig.mind.committed(vec![close(run, recorded())]);
rig.at(2);
let LaunchOutcome::Launched { run: persona } = rig.launch(RunTurn::PersonaTurn, &[]) else { panic!("not launched") };
rig.at(3);
assert_eq!(rig.launch(RunTurn::Sleep, &[]), LaunchOutcome::Busy { run: persona }, "a live Persona session holds the slot a night needs");
}
#[test]
fn a_sleep_run_takes_no_claims() {
let rig = Rig::new();
assert_eq!(rig.launch(RunTurn::Sleep, &[spec_ref("x")]), LaunchOutcome::Refused(Declined::PersonaRunTakesNoClaims));
assert_eq!(rig.runs(), 0);
}
#[test]
fn the_unit_started_names_the_minds_instance() {
let rig = Rig::new();

View file

@ -193,7 +193,7 @@ pub(crate) fn people_of(memory: &eureka_pipeline::PipelineMemory) -> Vec<String>
let origin: &[eureka_pipeline::Short] = match &memory.origin {
MemoryOrigin::Room { heard_from, .. } => heard_from,
MemoryOrigin::Direct { with, .. } => with,
MemoryOrigin::Wake | MemoryOrigin::SelfRun { .. } => &[],
MemoryOrigin::Wake | MemoryOrigin::SelfRun { .. } | MemoryOrigin::Sleep { .. } => &[],
};
memory.about.iter().chain(origin).map(|id| id.0.clone()).collect()
}
@ -206,6 +206,7 @@ pub(crate) fn origin_phrase(origin: &MemoryOrigin, names: &BTreeMap<&str, &str>)
MemoryOrigin::Direct { with, .. } => format!("privately, with {}", who(with.iter().map(|id| id.0.as_str()), names)),
MemoryOrigin::Wake => "on a wake".to_string(),
MemoryOrigin::SelfRun { run } => format!("in your Self run {}", run.0),
MemoryOrigin::Sleep { run } => format!("in your sleep {}", run.0),
}
}

View file

@ -55,12 +55,17 @@ pub(crate) struct Recording {
pub(crate) fail: Cell<bool>,
/// One named instance whose unit never starts.
pub(crate) fail_for: RefCell<Option<Slug>>,
running: RefCell<Vec<(Slug, RunTurn)>>,
running: RefCell<Vec<(Slug, eureka_pipeline::RunSlot)>>,
}
impl Recording {
pub(crate) fn kill(&self, turn: RunTurn) {
self.running.borrow_mut().retain(|unit| *unit != (Slug(INSTANCE.into()), turn));
self.running.borrow_mut().retain(|unit| *unit != (Slug(INSTANCE.into()), turn.slot()));
}
/// `kill` for a named instance.
pub(crate) fn kill_as(&self, instance: &Slug, turn: RunTurn) {
self.running.borrow_mut().retain(|unit| *unit != (instance.clone(), turn.slot()));
}
pub(crate) fn revive(&self, turn: RunTurn) {
@ -69,7 +74,7 @@ impl Recording {
/// `revive` for a named instance.
pub(crate) fn revive_as(&self, instance: &Slug, turn: RunTurn) {
self.running.borrow_mut().push((instance.clone(), turn));
self.running.borrow_mut().push((instance.clone(), turn.slot()));
}
}
@ -79,12 +84,12 @@ impl Launcher for Recording {
return Err(anyhow!("refused"));
}
self.started.borrow_mut().push((instance.clone(), turn));
self.running.borrow_mut().push((instance.clone(), turn));
self.running.borrow_mut().push((instance.clone(), turn.slot()));
Ok(())
}
fn alive(&self, instance: &Slug, turn: RunTurn) -> bool {
self.running.borrow().contains(&(instance.clone(), turn))
self.running.borrow().contains(&(instance.clone(), turn.slot()))
}
}

View file

@ -131,6 +131,27 @@
"SelfRun"
],
"type": "object"
},
{
"additionalProperties": false,
"description": "Her Sleep run, by its label: a memory she formed in her night.",
"properties": {
"Sleep": {
"properties": {
"run": {
"$ref": "#/$defs/Short"
}
},
"required": [
"run"
],
"type": "object"
}
},
"required": [
"Sleep"
],
"type": "object"
}
]
},

View file

@ -96,6 +96,27 @@
"SelfRun"
],
"type": "object"
},
{
"additionalProperties": false,
"description": "Her Sleep run, by its label: a memory she formed in her night.",
"properties": {
"Sleep": {
"properties": {
"run": {
"$ref": "#/$defs/Short"
}
},
"required": [
"run"
],
"type": "object"
}
},
"required": [
"Sleep"
],
"type": "object"
}
]
},

View file

@ -61,7 +61,8 @@
"RunTurn": {
"enum": [
"PersonaTurn",
"SelfRun"
"SelfRun",
"Sleep"
],
"type": "string"
},

View file

@ -663,6 +663,27 @@
"required": [
"SelfRun"
]
},
{
"description": "Her Sleep run, by its label: a memory she formed in her night.",
"type": "object",
"properties": {
"Sleep": {
"type": "object",
"properties": {
"run": {
"$ref": "#/$defs/Short"
}
},
"required": [
"run"
]
}
},
"additionalProperties": false,
"required": [
"Sleep"
]
}
]
},
@ -2611,7 +2632,8 @@
"type": "string",
"enum": [
"PersonaTurn",
"SelfRun"
"SelfRun",
"Sleep"
]
},
"SemanticQuery": {

View file

@ -791,6 +791,27 @@
"required": [
"SelfRun"
]
},
{
"description": "Her Sleep run, by its label: a memory she formed in her night.",
"type": "object",
"properties": {
"Sleep": {
"type": "object",
"properties": {
"run": {
"$ref": "#/$defs/Short"
}
},
"required": [
"run"
]
}
},
"additionalProperties": false,
"required": [
"Sleep"
]
}
]
},
@ -1184,6 +1205,31 @@
"UnknownSupersessor"
]
},
{
"description": "A consolidation and the supersession of the memory it consumes come together: `memory` consolidates `consumed`, or is named as superseding it, without the other half in the batch.",
"type": "object",
"properties": {
"UnpairedConsolidation": {
"type": "object",
"properties": {
"consumed": {
"type": "string"
},
"memory": {
"type": "string"
}
},
"required": [
"memory",
"consumed"
]
}
},
"additionalProperties": false,
"required": [
"UnpairedConsolidation"
]
},
{
"type": "object",
"properties": {
@ -5335,7 +5381,8 @@
"type": "string",
"enum": [
"PersonaTurn",
"SelfRun"
"SelfRun",
"Sleep"
]
},
"Sha": {