loopd: a turn's speaker in the log, its number in SessionUpdated
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
+20
-13
@@ -178,25 +178,32 @@ pub fn handle(stream: UnixStream, ctx: Arc<Context>) {
|
||||
}
|
||||
};
|
||||
|
||||
let outcome = run_turn(&mut session, &runtime, &turn.content, &mut |event| {
|
||||
let _ = write_frame(
|
||||
&mut stream,
|
||||
&Envelope {
|
||||
v: PROTOCOL_VERSION,
|
||||
id: request.id,
|
||||
r#final: false,
|
||||
msg: Message::TurnEvent(event.clone()),
|
||||
},
|
||||
);
|
||||
});
|
||||
let outcome = run_turn(
|
||||
&mut session,
|
||||
&runtime,
|
||||
&turn.content,
|
||||
turn.speaker.as_deref(),
|
||||
&mut |event| {
|
||||
let _ = write_frame(
|
||||
&mut stream,
|
||||
&Envelope {
|
||||
v: PROTOCOL_VERSION,
|
||||
id: request.id,
|
||||
r#final: false,
|
||||
msg: Message::TurnEvent(event.clone()),
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// 5. Release the session before the final frame, so the next turn does not find it busy.
|
||||
drop(held);
|
||||
|
||||
// A finished turn's records are for `memoryd` to index; best effort, since `memoryd` catches
|
||||
// up at its next start.
|
||||
// up at its next start. The number is this turn's: its `User` record is the last one counted.
|
||||
// A failed turn is not told, as before, though its logged message still counts.
|
||||
if outcome.is_ok()
|
||||
&& let Err(e) = ctx.memory.updated(&turn.session)
|
||||
&& let Err(e) = ctx.memory.updated(&turn.session, session.turns())
|
||||
{
|
||||
eprintln!(
|
||||
"loopd: memoryd was not told about {}: {e}",
|
||||
|
||||
@@ -22,8 +22,9 @@ pub const MEMORY_WAIT: Duration = Duration::from_secs(10);
|
||||
pub trait MemorySource: Send + Sync {
|
||||
/// The block for a new session. `Err` ends in `UNAVAILABLE`: no session starts without it.
|
||||
fn block(&self) -> Result<String, String>;
|
||||
/// The session has new records; best effort, a failure is only logged by the caller.
|
||||
fn updated(&self, session: &SessionId) -> Result<(), String>;
|
||||
/// The session's turn number `turn` has ended, and its records are in the log; best effort,
|
||||
/// a failure is only logged by the caller.
|
||||
fn updated(&self, session: &SessionId, turn: u64) -> Result<(), String>;
|
||||
}
|
||||
|
||||
/// No memory: for tests and the self-test, never for `loopd serve`.
|
||||
@@ -33,7 +34,7 @@ impl MemorySource for NoMemory {
|
||||
fn block(&self) -> Result<String, String> {
|
||||
Ok(String::new())
|
||||
}
|
||||
fn updated(&self, _session: &SessionId) -> Result<(), String> {
|
||||
fn updated(&self, _session: &SessionId, _turn: u64) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -91,11 +92,10 @@ impl MemorySource for MemoryPort {
|
||||
}
|
||||
}
|
||||
|
||||
fn updated(&self, session: &SessionId) -> Result<(), String> {
|
||||
fn updated(&self, session: &SessionId, turn: u64) -> Result<(), String> {
|
||||
match self.ask(Message::SessionUpdated(SessionUpdated {
|
||||
session: session.clone(),
|
||||
// Set in M5c-1 task 02. `memoryd` does not read it before M5c-1 task 09.
|
||||
turn: 0,
|
||||
turn,
|
||||
}))? {
|
||||
Message::Ok(_) => Ok(()),
|
||||
Message::Error(e) => Err(format!("memoryd: {}", e.detail)),
|
||||
|
||||
@@ -208,6 +208,19 @@ impl Session {
|
||||
})
|
||||
}
|
||||
|
||||
/// How many turns the session has had: its `User` records, one per turn that reached the
|
||||
/// log, a failed turn's too. Counted from `records`, which `open` reads back whole, so the
|
||||
/// count goes on after a reopen or a restart; right after a turn it is that turn's number,
|
||||
/// the first being 1. Epoch 0 is a session's only log until compaction is built.
|
||||
pub fn turns(&self) -> u64 {
|
||||
let users = self
|
||||
.records
|
||||
.iter()
|
||||
.filter(|record| matches!(record, LogRecord::User { .. }))
|
||||
.count();
|
||||
u64::try_from(users).unwrap_or(u64::MAX)
|
||||
}
|
||||
|
||||
pub fn last_usage(&self) -> Option<bxcore::Usage> {
|
||||
for record in self.records.iter().rev() {
|
||||
if let LogRecord::Usage {
|
||||
|
||||
@@ -113,19 +113,23 @@ fn map_event(event: &ChatEvent, on_event: &mut dyn FnMut(&TurnEvent)) {
|
||||
}
|
||||
}
|
||||
|
||||
/// One turn of `session`: `content` is the user's message, and `speaker` who spoke it, when one
|
||||
/// person did. The speaker is logged on the `User` record, for `memoryd`, and never sent to the
|
||||
/// model: the request is the same with a speaker and without one (M5c spec, section 11).
|
||||
pub fn run_turn(
|
||||
session: &mut Session,
|
||||
rt: &Runtime<'_>,
|
||||
content: &str,
|
||||
speaker: Option<&str>,
|
||||
on_event: &mut dyn FnMut(&TurnEvent),
|
||||
) -> Result<TurnOutcome, TurnError> {
|
||||
session
|
||||
.append(LogRecord::User {
|
||||
time: Timestamp::now(),
|
||||
content: neutralise(content).into_owned(),
|
||||
// Set in M5c-1 task 02, from the turn. `None` is not written: the record's bytes
|
||||
// are as before.
|
||||
speaker: None,
|
||||
// As it came: not checked and not neutralised, since it never reaches the model.
|
||||
// `None` is not written, so the record's bytes are as before M5c.
|
||||
speaker: speaker.map(str::to_string),
|
||||
})
|
||||
.map_err(TurnError::Session)?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user