bxcore: a turn's speaker, SessionUpdated's turn, the session-state messages

Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
2026-09-28 10:27:28 -07:00
co-authored by ornith-1.5-35b-a3b
parent 027146f359
commit c798714af1
31 changed files with 238 additions and 13 deletions
+2 -1
View File
@@ -38,7 +38,8 @@ pub use ids::{CallId, Epoch, Hash32, SessionId, Timestamp, ValueError};
pub use log::{LogRecord, ToolCall, Usage};
pub use memory::{
ConsolidateEntry, EntryAction, MemoryAdmin, MemoryAdminReply, MemoryArgs, MemoryBlockReply,
MemoryCall, MemoryCommand, MemoryFile, RecallArgs, Reindexed, SessionUpdated,
MemoryCall, MemoryCommand, MemoryFile, RecallArgs, Reindexed, SessionState, SessionStateReply,
SessionUpdated,
};
pub use wire::{
ApprovalList, Approve, ApproveResult, DenyReason, Empty, Envelope, ErrorCode, GrantProblem,
+4
View File
@@ -37,6 +37,10 @@ pub enum LogRecord {
User {
time: Timestamp,
content: String,
/// The turn's speaker (M5c spec, section 11). Written only when present, so a log written
/// before M5c reads as it is, and a turn without a speaker is logged as before.
#[serde(default, skip_serializing_if = "Option::is_none")]
speaker: Option<String>,
},
Assistant {
time: Timestamp,
+20 -1
View File
@@ -228,11 +228,30 @@ pub struct MemoryBlockReply {
pub text: String,
}
/// `loopd` → `memoryd`: this session's log has new records.
/// `loopd` → `memoryd`: this session's log has new records, those of a turn that has ended.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SessionUpdated {
pub session: SessionId,
/// The ended turn's number in the session (M5c spec, section 5). It has no default: a frame
/// without it is refused, never read as turn 0.
pub turn: u64,
}
/// `memoryd` → `brokerd`, on `state.sock`: the session's state now (M5c spec, section 7).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SessionState {
pub session: SessionId,
}
/// `brokerd` → `memoryd`: the session's class, and whether it has read untrusted content. Neither
/// field has a default: a reply without one is refused, never read as a clean session.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SessionStateReply {
pub class: DataClass,
pub untrusted: bool,
}
/// `brokerd` → `memoryd`: a decided `recall` or `memory` call, with the session's state when it
+10 -2
View File
@@ -2,7 +2,8 @@ use serde::{Deserialize, Serialize};
use crate::gateway::{ApprovalOutcome, ApprovalPosted, ApprovalReply, ApprovalRequest};
use crate::memory::{
MemoryAdmin, MemoryAdminReply, MemoryBlockReply, MemoryCall, Reindexed, SessionUpdated,
MemoryAdmin, MemoryAdminReply, MemoryBlockReply, MemoryCall, Reindexed, SessionState,
SessionStateReply, SessionUpdated,
};
use crate::{CallId, DataClass, DecisionRecord, SessionId, Timestamp, Usage};
@@ -51,6 +52,8 @@ pub enum Message {
Reindexed(Reindexed),
MemoryAdmin(MemoryAdmin),
MemoryAdminReply(MemoryAdminReply),
SessionState(SessionState),
SessionStateReply(SessionStateReply),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -126,13 +129,18 @@ pub enum DenyReason {
StateUnreadable,
}
// JSON: {"kind":"turn","body":{"session":"…","content":"…","resume":false}}
// JSON: {"kind":"turn","body":{"session":"…","content":"…","resume":false,"speaker":"kyle"}}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Turn {
pub session: SessionId,
pub content: String,
pub resume: bool,
/// Who spoke the turn, when one person did (M5c spec, section 11). Written only when present,
/// and a frame without it reads as `None`, the safe side: a turn without a speaker is never
/// synced to Honcho.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub speaker: Option<String>,
}
// JSON: {"event":"content","text":"…"} — the tag sits beside the fields, snake_case