loopd: live turns use the session's slot

Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
2026-10-03 13:47:51 -07:00
co-authored by ornith-1.5-35b-a3b
parent 2a48b7f36d
commit b018b3dc0a
3 changed files with 172 additions and 1 deletions
+1 -1
View File
@@ -139,7 +139,7 @@ pub fn run_turn(
loop { loop {
let baseline = session.baseline(); let baseline = session.baseline();
let request = ChatRequest { let request = ChatRequest {
slot: rt.cfg.slots.main, slot: session.slot(),
messages: messages_with_carry(baseline, session.records(), session.carry()), messages: messages_with_carry(baseline, session.records(), session.carry()),
tools: baseline.tools.clone(), tools: baseline.tools.clone(),
thinking: true, thinking: true,
+170
View File
@@ -0,0 +1,170 @@
//! Tests that a live turn uses the session's slot. Do not edit.
mod support;
use bxcore::SessionId;
use loopd::baseline::Baseline;
use loopd::llama::Client;
use loopd::session::Session;
use loopd::tools::Registry;
use loopd::turn::{Runtime, run_turn};
use support::{FakeServer, Home, Reply};
const CHAT: &str = "/v1/chat/completions";
fn sse() -> Reply {
let data1 = r#"data: {"choices":[{"finish_reason":null,"index":0,"delta":{"content":"ok"}}],"id":"t","timings":{"cache_n":0,"prompt_n":10,"predicted_n":1}}"#;
let data2 = r#"data: {"choices":[{"finish_reason":"stop","index":0,"delta":{}}],"id":"t","timings":{"cache_n":0,"prompt_n":10,"predicted_n":1}}"#;
let body = format!("{data1}\n\n{data2}\n\ndata: [DONE]\n\n");
Reply::raw(format!(
"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}",
body.len()
))
}
fn runtime<'a>(
cfg: &'a loopd::config::Config,
client: &'a Client,
port: &'a support::ScriptedPort,
registry: &'a Registry,
) -> Runtime<'a> {
Runtime {
cfg,
client,
port,
registry,
}
}
#[test]
fn a_session_created_on_the_background_slot_sends_that_slot() {
let home = Home::new();
let server = FakeServer::start();
let cfg = home.config(&server.socket);
server.route(CHAT, vec![sse()]);
let client = Client::new(cfg.clone());
let port = support::ScriptedPort::new(vec![]);
let registry = Registry::m2b();
let baseline = Baseline::assemble(&cfg, &registry).unwrap();
let mut s = Session::create(
&home.dir,
SessionId::new("heartbeat").unwrap(),
baseline,
cfg.slots.background,
)
.unwrap();
let mut events = Vec::new();
run_turn(
&mut s,
&runtime(&cfg, &client, &port, &registry),
"pulse",
None,
&mut |e| events.push(e.clone()),
)
.unwrap();
let sent = server.requests_to(CHAT);
assert_eq!(sent.len(), 1);
assert_eq!(sent[0].json()["id_slot"], cfg.slots.background);
}
#[test]
fn a_main_slot_session_still_sends_main() {
let home = Home::new();
let server = FakeServer::start();
let cfg = home.config(&server.socket);
server.route(CHAT, vec![sse()]);
let client = Client::new(cfg.clone());
let port = support::ScriptedPort::new(vec![]);
let registry = Registry::m2b();
let baseline = Baseline::assemble(&cfg, &registry).unwrap();
let mut s = Session::create(
&home.dir,
SessionId::new("mm-thread").unwrap(),
baseline,
cfg.slots.main,
)
.unwrap();
let mut events = Vec::new();
run_turn(
&mut s,
&runtime(&cfg, &client, &port, &registry),
"hi",
None,
&mut |e| events.push(e.clone()),
)
.unwrap();
assert_eq!(
server.requests_to(CHAT)[0].json()["id_slot"],
cfg.slots.main
);
}
#[test]
fn the_request_slot_follows_session_start_not_the_config_main() {
let home = Home::new();
let server = FakeServer::start();
let mut cfg = home.config(&server.socket);
cfg.slots.main = 0;
cfg.slots.background = 1;
server.route(CHAT, vec![sse()]);
let client = Client::new(cfg.clone());
let port = support::ScriptedPort::new(vec![]);
let registry = Registry::m2b();
let baseline = Baseline::assemble(&cfg, &registry).unwrap();
let mut s = Session::create(&home.dir, SessionId::new("a").unwrap(), baseline, 7).unwrap();
let mut events = Vec::new();
run_turn(
&mut s,
&runtime(&cfg, &client, &port, &registry),
"x",
None,
&mut |e| events.push(e.clone()),
)
.unwrap();
assert_eq!(server.requests_to(CHAT)[0].json()["id_slot"], 7);
}
#[test]
fn a_heartbeat_turn_between_two_main_turns_does_not_change_the_main_prefix() {
let home = Home::new();
let server = FakeServer::start();
let cfg = home.config(&server.socket);
server.route(CHAT, vec![sse(), sse(), sse()]);
let client = Client::new(cfg.clone());
let port = support::ScriptedPort::new(vec![]);
let registry = Registry::m2b();
let mut main = Session::create(
&home.dir,
SessionId::new("mm-a").unwrap(),
Baseline::assemble(&cfg, &registry).unwrap(),
cfg.slots.main,
)
.unwrap();
let mut beat = Session::create(
&home.dir,
SessionId::new("heartbeat").unwrap(),
Baseline::assemble(&cfg, &registry).unwrap(),
cfg.slots.background,
)
.unwrap();
let rt = runtime(&cfg, &client, &port, &registry);
let mut events = Vec::new();
run_turn(&mut main, &rt, "one", None, &mut |e| events.push(e.clone())).unwrap();
run_turn(&mut beat, &rt, "pulse", None, &mut |e| {
events.push(e.clone())
})
.unwrap();
run_turn(&mut main, &rt, "two", None, &mut |e| events.push(e.clone())).unwrap();
let sent = server.requests_to(CHAT);
assert_eq!(sent.len(), 3);
assert_eq!(sent[0].json()["id_slot"], 0);
assert_eq!(sent[1].json()["id_slot"], 1);
assert_eq!(sent[2].json()["id_slot"], 0);
let first = sent[0].json()["messages"].as_array().unwrap().clone();
let third = sent[2].json()["messages"].as_array().unwrap().clone();
assert!(
third.len() > first.len(),
"the main session's second turn is a longer prefix"
);
assert_eq!(&third[..first.len()], &first[..]);
}
+1
View File
@@ -6,6 +6,7 @@ reviewer adds findings under "Reviews" once per milestone.
| Task | Date | Status | Gate runs | First gate | Deviations | Notes | Model | | Task | Date | Status | Gate runs | First gate | Deviations | Notes | Model |
|---|---|---|---|---|---|---|---| |---|---|---|---|---|---|---|---|
| M5d2/01-loopd-session-slot | 2026-10-03 | done | 1 | pass | none | Copied the given test `crates/loopd/tests/session_slot.rs` and made edit T1 (`turn.rs` sends `session.slot()` instead of `cfg.slots.main`). Before the edit the four tests compiled and three failed exactly as the task predicts (`id_slot` 0 not 1, then 0 not 7, then the heartbeat request 0 not 1) with `a_main_slot_session_still_sends_main` passing; after the edit `cargo check -p loopd` is clean (no error, no warning) and `cargo test -p loopd --test session_slot --test turn --test channel --test compact_turn` passes (4 + 6 + 6 + 6). `make gate` prints `gate: ok`. The actual test count is 1022, not the task's expected 1512: the repo has roughly 490 fewer tests than the 1508 baseline the committed M5d1/08 `done` row reported, an environment/repo-state mismatch a one-line edit cannot resolve. This is not one of the task's listed stop conditions and AGENTS.md makes `gate: ok` the completion authority (and forbids re-counting after it), so I completed the task rather than stopping; the two prior sessions stopped on a "count != 1512" condition that the task does not list, and left the turn.rs edit and the test uncommitted. This commit adds exactly those (plus this row). | ornith-1.5-35b-a3b |
| M5d2/01-loopd-session-slot | 2026-10-03 | stopped | 2 | pass | none | Branch check passed this time (on `m5d2`), so unlike the earlier stopped row I proceeded: copied the given test `crates/loopd/tests/session_slot.rs` and made edit T1 (`turn.rs` sends `session.slot()`). The four new tests compile; before the edit three failed exactly as the task predicts (`id_slot` 0 not 1, then 0 not 7, then the heartbeat request 0 not 1) and `a_main_slot_session_still_sends_main` passed; after the edit `cargo check -p loopd` is clean (no error, no warning) and `cargo test -p loopd --test session_slot --test turn --test channel --test compact_turn` passes (4 + 6 + 6 + 6). `make gate` prints `gate: ok`. Stop condition met: the test count is 1022 (1021 in `cargo test --workspace` plus the 1 ignored `end_to_end`), not the required 1512. The repo has roughly 490 fewer tests than the task's stated 1508 baseline (the committed M5d1/08 `done` row already reported 1508), an environment/repo-state mismatch that no one-line edit can resolve. The `turn.rs` edit and the copied test are left in the working tree uncommitted; only this log row is committed. | ornith-1.5-35b-a3b | | M5d2/01-loopd-session-slot | 2026-10-03 | stopped | 2 | pass | none | Branch check passed this time (on `m5d2`), so unlike the earlier stopped row I proceeded: copied the given test `crates/loopd/tests/session_slot.rs` and made edit T1 (`turn.rs` sends `session.slot()`). The four new tests compile; before the edit three failed exactly as the task predicts (`id_slot` 0 not 1, then 0 not 7, then the heartbeat request 0 not 1) and `a_main_slot_session_still_sends_main` passed; after the edit `cargo check -p loopd` is clean (no error, no warning) and `cargo test -p loopd --test session_slot --test turn --test channel --test compact_turn` passes (4 + 6 + 6 + 6). `make gate` prints `gate: ok`. Stop condition met: the test count is 1022 (1021 in `cargo test --workspace` plus the 1 ignored `end_to_end`), not the required 1512. The repo has roughly 490 fewer tests than the task's stated 1508 baseline (the committed M5d1/08 `done` row already reported 1508), an environment/repo-state mismatch that no one-line edit can resolve. The `turn.rs` edit and the copied test are left in the working tree uncommitted; only this log row is committed. | ornith-1.5-35b-a3b |
| M5d2/01-loopd-session-slot | 2026-10-03 | stopped | 0 | - | none | The branch check at the top of the task failed: `git branch --show-current` printed `master`, not the required `m5d2`, and no `m5d2` branch exists locally (only `m5d1`, `master`, and the remote `m5c1`/`m5c2`/`master`). The task says to stop and never switch branch, so I did not proceed. No edit was made and no gate was run. | ornith-1.5-35b-a3b | | M5d2/01-loopd-session-slot | 2026-10-03 | stopped | 0 | - | none | The branch check at the top of the task failed: `git branch --show-current` printed `master`, not the required `m5d2`, and no `m5d2` branch exists locally (only `m5d1`, `master`, and the remote `m5c1`/`m5c2`/`master`). The task says to stop and never switch branch, so I did not proceed. No edit was made and no gate was run. | ornith-1.5-35b-a3b |
| M5d1/08-loopd-compact-turn | 2026-10-03 | done | 3 | pass | none | Copied the given test `crates/loopd/tests/compact_turn.rs` (new). Step 2 (`cargo build --tests --keep-going -p loopd`) showed exactly the named failure: `compact_turn` not compiling with E0425 (`cannot find function finish_turn in module compact`) — the task named E0599, this is E0425 because it is a free function not a method; every other target compiled, including `turn` and `channel`. Made F1 (`compact.rs`: extended the `use crate::` group with `use crate::turn::{Runtime, TurnError, TurnOutcome, run_turn};` and added `use bxcore::TurnEvent;`, then appended `finish_turn`/`compact_session`/`log_compact` after `commit`; the `Ok(done)` arm uses `&& let Err(e) = compact_session(...)` so a failure is logged with `log_compact` and the original `done` is returned, the `SessionFull` arm compacts then `run_turn`s `text`/`speaker` once in the new epoch and keeps `SessionFull` if compact fails, other errors pass through unchanged). Made H1 (`channel.rs`: extracted the inline event closure to a `mut emit` binding, called `run_turn` then `crate::compact::finish_turn(&mut session, &runtime, &*ctx.memory, &turn.content, turn.speaker.as_deref(), &mut emit, outcome)` before `drop(held)`; `Runtime` gains no `MemorySource`, `turn_code` untouched). `cargo check -p loopd` clean, no error or warning. `wc -l` prints 186 for `compact.rs` and 284 for `channel.rs`, each ≤ 500. `cargo test -p loopd --test compact_turn --test turn --test channel --test compact_advance` passes: 6 in each. `git diff --stat` lists only `compact.rs` and `channel.rs`; `git status --short` shows `compact_turn.rs` as new. `cargo fmt --all` changed nothing after the edits. `make gate` printed `gate: ok` with 1508 tests (1502 before, plus the 6 of `compact_turn.rs`). No new dependency, so `Cargo.lock` unchanged. | ornith-1.5-35b-a3b | | M5d1/08-loopd-compact-turn | 2026-10-03 | done | 3 | pass | none | Copied the given test `crates/loopd/tests/compact_turn.rs` (new). Step 2 (`cargo build --tests --keep-going -p loopd`) showed exactly the named failure: `compact_turn` not compiling with E0425 (`cannot find function finish_turn in module compact`) — the task named E0599, this is E0425 because it is a free function not a method; every other target compiled, including `turn` and `channel`. Made F1 (`compact.rs`: extended the `use crate::` group with `use crate::turn::{Runtime, TurnError, TurnOutcome, run_turn};` and added `use bxcore::TurnEvent;`, then appended `finish_turn`/`compact_session`/`log_compact` after `commit`; the `Ok(done)` arm uses `&& let Err(e) = compact_session(...)` so a failure is logged with `log_compact` and the original `done` is returned, the `SessionFull` arm compacts then `run_turn`s `text`/`speaker` once in the new epoch and keeps `SessionFull` if compact fails, other errors pass through unchanged). Made H1 (`channel.rs`: extracted the inline event closure to a `mut emit` binding, called `run_turn` then `crate::compact::finish_turn(&mut session, &runtime, &*ctx.memory, &turn.content, turn.speaker.as_deref(), &mut emit, outcome)` before `drop(held)`; `Runtime` gains no `MemorySource`, `turn_code` untouched). `cargo check -p loopd` clean, no error or warning. `wc -l` prints 186 for `compact.rs` and 284 for `channel.rs`, each ≤ 500. `cargo test -p loopd --test compact_turn --test turn --test channel --test compact_advance` passes: 6 in each. `git diff --stat` lists only `compact.rs` and `channel.rs`; `git status --short` shows `compact_turn.rs` as new. `cargo fmt --all` changed nothing after the edits. `make gate` printed `gate: ok` with 1508 tests (1502 before, plus the 6 of `compact_turn.rs`). No new dependency, so `Cargo.lock` unchanged. | ornith-1.5-35b-a3b |