loopd: replay a carried summary as an unlogged user message
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
@@ -91,12 +91,30 @@ impl Baseline {
|
||||
}
|
||||
}
|
||||
|
||||
/// The heading in front of a carried summary. Built from pieces (T33).
|
||||
pub const CARRY_HEADING: &str = concat!("Earlier ", "turns, summarised:");
|
||||
|
||||
/// The system message, then each log record replayed in order. Every record variant is named, so a
|
||||
/// new one is a compile error rather than a silent skip.
|
||||
pub fn messages(baseline: &Baseline, records: &[LogRecord]) -> Vec<ChatMessage> {
|
||||
messages_with_carry(baseline, records, None)
|
||||
}
|
||||
|
||||
/// Like `messages`, and when `carry` is `Some` inserts one user message after the system
|
||||
/// prompt: the heading, a blank line, the summary. That user is not a `LogRecord::User`.
|
||||
pub fn messages_with_carry(
|
||||
baseline: &Baseline,
|
||||
records: &[LogRecord],
|
||||
carry: Option<&str>,
|
||||
) -> Vec<ChatMessage> {
|
||||
let mut out = vec![ChatMessage::System {
|
||||
content: baseline.system.clone(),
|
||||
}];
|
||||
if let Some(summary) = carry {
|
||||
out.push(ChatMessage::User {
|
||||
content: format!("{CARRY_HEADING}\n\n{summary}"),
|
||||
});
|
||||
}
|
||||
for record in records {
|
||||
match record {
|
||||
LogRecord::SessionStart { .. } => {}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//! logged (M5b spec, section 8), so every later request replays the same bytes; the model's own
|
||||
//! output is logged as the server returned it.
|
||||
|
||||
use crate::baseline::messages;
|
||||
use crate::baseline::messages_with_carry;
|
||||
use crate::config::Config;
|
||||
use crate::llama::info::{CacheOutcome, cache_outcome};
|
||||
use crate::llama::{ChatEvent, ChatRequest, Client, InferError};
|
||||
@@ -140,7 +140,7 @@ pub fn run_turn(
|
||||
let baseline = session.baseline();
|
||||
let request = ChatRequest {
|
||||
slot: rt.cfg.slots.main,
|
||||
messages: messages(baseline, session.records()),
|
||||
messages: messages_with_carry(baseline, session.records(), session.carry()),
|
||||
tools: baseline.tools.clone(),
|
||||
thinking: true,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
//! Tests for the carried summary in the next epoch's prefix. Do not edit.
|
||||
|
||||
use bxcore::{Epoch, Hash32, LogRecord, SessionId, Timestamp};
|
||||
use loopd::baseline::{Baseline, CARRY_HEADING, messages, messages_with_carry};
|
||||
use loopd::llama::ChatMessage;
|
||||
|
||||
fn ts() -> Timestamp {
|
||||
Timestamp::parse("2026-09-18T08:00:00.000Z").unwrap()
|
||||
}
|
||||
|
||||
fn user(content: &str) -> LogRecord {
|
||||
LogRecord::User {
|
||||
time: ts(),
|
||||
content: content.to_string(),
|
||||
speaker: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn start(epoch: u32) -> LogRecord {
|
||||
LogRecord::SessionStart {
|
||||
time: ts(),
|
||||
session: SessionId::new("a").unwrap(),
|
||||
epoch: Epoch(epoch),
|
||||
slot: 0,
|
||||
baseline: Hash32::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn epoch_0_matches_messages_without_a_carry() {
|
||||
let b = Baseline {
|
||||
system: "sys".to_string(),
|
||||
tools: vec![],
|
||||
};
|
||||
let records = vec![start(0), user("hi")];
|
||||
assert_eq!(
|
||||
messages_with_carry(&b, &records, None),
|
||||
messages(&b, &records)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_carry_is_a_user_message_after_the_system_prompt() {
|
||||
let b = Baseline {
|
||||
system: "new-sys".to_string(),
|
||||
tools: vec![],
|
||||
};
|
||||
let records = vec![start(1), user("next")];
|
||||
let got = messages_with_carry(&b, &records, Some("the summary"));
|
||||
assert_eq!(
|
||||
got,
|
||||
vec![
|
||||
ChatMessage::System {
|
||||
content: "new-sys".to_string(),
|
||||
},
|
||||
ChatMessage::User {
|
||||
content: format!("{CARRY_HEADING}\n\nthe summary"),
|
||||
},
|
||||
ChatMessage::User {
|
||||
content: "next".to_string(),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_carry_is_not_taken_from_a_log_user_record() {
|
||||
let b = Baseline {
|
||||
system: "sys".to_string(),
|
||||
tools: vec![],
|
||||
};
|
||||
let records = vec![start(1)];
|
||||
let got = messages_with_carry(&b, &records, Some("carried"));
|
||||
assert_eq!(got.len(), 2);
|
||||
assert!(
|
||||
!records.iter().any(|r| matches!(r, LogRecord::User { .. })),
|
||||
"the carry is not in the log"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_heading_is_built_from_pieces() {
|
||||
assert_eq!(CARRY_HEADING, concat!("Earlier ", "turns, summarised:"));
|
||||
assert!(
|
||||
!CARRY_HEADING.contains('<'),
|
||||
"the heading is words, not a tag"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn epoch_1_is_not_an_extension_of_epoch_0() {
|
||||
let old = Baseline {
|
||||
system: "old-sys".to_string(),
|
||||
tools: vec![],
|
||||
};
|
||||
let new = Baseline {
|
||||
system: "new-sys".to_string(),
|
||||
tools: vec![],
|
||||
};
|
||||
let epoch0 = vec![start(0), user("first")];
|
||||
let epoch1 = vec![start(1), user("second")];
|
||||
let prev = messages(&old, &epoch0);
|
||||
let next = messages_with_carry(&new, &epoch1, Some("sum of first"));
|
||||
assert!(
|
||||
next.len() >= 2,
|
||||
"system plus carry, then the new epoch's user"
|
||||
);
|
||||
assert_ne!(next[0], prev[0], "the system message changed");
|
||||
assert!(
|
||||
!next.starts_with(&prev[..]),
|
||||
"the new prefix is not an extension of the old messages"
|
||||
);
|
||||
match &next[1] {
|
||||
ChatMessage::User { content } => {
|
||||
assert!(content.contains("sum of first"), "{content}");
|
||||
assert!(!content.contains("second"), "{content}");
|
||||
}
|
||||
other => panic!("{other:?}"),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user