loopd: neutralise user messages, tool results and the memory block

Text from outside the model (the user's message, every tool result, and the
memory block) is neutralised the moment it is first logged, so every later
request replays the same bytes and a tag in that text cannot reach the model as
one of its own control tokens. The model's own output, replay, and the system
prompt are left byte-for-byte.

Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
2026-09-27 19:08:42 -07:00
co-authored by ornith-1.5-35b-a3b
parent ca8b50cd69
commit b4d631e249
6 changed files with 621 additions and 4 deletions
+6 -3
View File
@@ -7,6 +7,7 @@ use std::path::{Path, PathBuf};
use crate::config::Config;
use crate::llama::{ChatMessage, ToolSchema};
use crate::tools::Registry;
use bxcore::sanitize::neutralise;
use bxcore::{Hash32, LogRecord, sha256};
use serde::Deserialize;
@@ -46,7 +47,9 @@ impl Baseline {
}
/// The system prompt trimmed, then a blank line and the memory block `memoryd` rendered (M5a
/// spec, section 4), when there is one; the core tool schemas.
/// spec, section 4), when there is one; the core tool schemas. The block is neutralised here
/// (M5b spec, section 8): `soul.md` is not checked on write, and the snapshot of the baseline
/// is what every request of the session sends.
pub fn assemble_with(
cfg: &Config,
registry: &Registry,
@@ -55,10 +58,10 @@ impl Baseline {
let system = std::fs::read_to_string(&cfg.baseline.system)
.map_err(|e| BaselineError::Read(cfg.baseline.system.clone(), e))?;
let mut system = system.trim_end().to_string();
let memory = memory.trim();
let memory = neutralise(memory.trim());
if !memory.is_empty() {
system.push_str("\n\n");
system.push_str(memory);
system.push_str(&memory);
}
Ok(Baseline {
system,