Eight closed tasks for epochs and compaction, then ten for the scheduler, ToolSummary, and tiers. Given tests first. Gate 1456 → 1507 → 1572. Co-Authored-By: Grok 4.6 <noreply@x.ai>
9.2 KiB
M5d-1 task 06: the summary call, and session-compact-failed
Branch: m5d1. Check it: git branch --show-current prints m5d1 and git status --short
is empty; otherwise stop. Never switch branch.
Commit subject: loopd: compact summary on the background slot
Log row: the Task cell is M5d1/06-loopd-compact-summary.
Goal
Compaction asks for a summary of the current epoch on the background slot, with no tools,
thinking on (spec 3.4). The last user message is compact.md (task 01's compact_prompt),
trimmed at the end. The completion is tag-neutralised and cut to summary_chars.
If that call is context-full (exceed_context_size_error), use task 05's fallback_summary.
If compact.md cannot be read, or the call fails for any other reason, return
CompactError::Failed whose Display ends with the runbook pointer
see docs/runbook.md#session-compact-failed. There is no fallback for a missing prompt.
This task does not write the next epoch. Task 07 does.
Every place that changes
| Place | What happens there |
|---|---|
crates/loopd/src/compact.rs |
CompactError; summarise (edits E1, E2) |
docs/runbook.md |
append the given session-compact-failed entry |
crates/loopd/tests/compact_summary.rs |
new, given |
| everything else | no change: not turn.rs, not channel.rs, not session.rs |
Files
- Copy, from
docs/plans/M5d1/files/(the same path under it): the new testcrates/loopd/tests/compact_summary.rs. - Append, from
docs/plans/M5d1/files/runbook-session-compact-failed.md, todocs/runbook.md. - Modify:
crates/loopd/src/compact.rs,docs/runbook.md,docs/implementer-log.md. - Do not touch: any other file.
config/compact.mdis already in the tree from task 05.
Facts you need
- The request.
ChatRequest { slot: cfg.slots.background, messages, tools: Vec::new(), thinking: true }. Emptytoolsis omitted from the JSON (skip_serializing_ifon the body). The given test assertsbody.get("tools").is_none()andid_slot == 1(the test config's background slot). - The messages.
messages_with_carry(session.baseline(), session.records(), session.carry()), then oneChatMessage::Userwhose content iscompact.mdaftertrim_end. Epoch 0 has no carry; later epochs keep the carried summary in the prefix so the model summarises what the session actually sends. - Read the prompt with
std::fs::read_to_string(&cfg.compact_prompt()). An I/O error isFailed { detail: format!("{}: {e}", path.display()) }so the path (andcompact.md) appears in the message. - The call.
client.chat_with_retry(&request, &mut |_| {}). A 400 that is not context-full is not retried (is_retryableis already false for it). - Context-full.
crate::turn::is_context_full(&e)thenfallback_summary(session.records(), cfg.compact.summary_chars). Do not fall back for a missing file or any otherInferError. - The text.
completion.content.unwrap_or_default(), thenbxcore::sanitize::neutralise, thencap_chars(..., cfg.compact.summary_chars). Neutralise first so a tag is not split by the cap. Display."{detail}; see docs/runbook.md#session-compact-failed". The given tests box the error asdyn Errorand checkto_string().compact.rsstays ≤ 500 lines. After this task it is about 120.- The items the edits use. This table is complete.
Item What it does Config::compact_prompttask 01, compact.mdbesidesystem.mdmessages_with_carrytask 03 Client::chat_with_retrysame as a live turn turn::is_context_fullthe one 400 exceed_context_size_errorbxcore::sanitize::neutralisetags to a visible look-alike fallback_summary,cap_charstask 05 ChatRequest.tools: Vec::new()omits toolson the wire
The edits, exactly
Nothing else in compact.rs changes. Each quoted "before" block occurs exactly once after
task 05. Keep the fields in the order shown.
crates/loopd/src/compact.rs
E1. The module doc and the use line. The lines
//! Compaction: when a session starts a new epoch, the summary call, and writing the next files
//! (M5d spec, section 3). Later tasks fill the call and the write.
use bxcore::{LogRecord, Usage};
become
//! Compaction: when a session starts a new epoch, the summary call, and writing the next files
//! (M5d spec, section 3). Task 07 writes the next files; task 08 calls this after a turn.
use bxcore::sanitize::neutralise;
use bxcore::{LogRecord, Usage};
use crate::baseline::messages_with_carry;
use crate::config::Config;
use crate::llama::{ChatMessage, ChatRequest, Client};
use crate::session::Session;
use crate::turn::is_context_full;
/// Why a summary call or a next-epoch write failed. Every variant's `Display` ends with the
/// runbook pointer `session-compact-failed`.
#[derive(Debug)]
pub enum CompactError {
Failed { detail: String },
}
impl std::fmt::Display for CompactError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CompactError::Failed { detail } => write!(
f,
"{detail}; see docs/runbook.md#session-compact-failed"
),
}
}
}
impl std::error::Error for CompactError {}
E2. After cap_chars (the function that ends s.chars().take(n).collect() / }), add
summarise. Copy this function whole. There is no todo!().
/// One completion on the background slot: no tools, thinking on, last user `compact.md`.
/// Context-full uses `fallback_summary`. A missing prompt, or any other infer error, is
/// `CompactError::Failed` with the runbook pointer. No fallback for a missing prompt.
pub fn summarise(cfg: &Config, client: &Client, session: &Session) -> Result<String, CompactError> {
let path = cfg.compact_prompt();
let prompt = std::fs::read_to_string(&path).map_err(|e| CompactError::Failed {
detail: format!("{}: {e}", path.display()),
})?;
let mut messages = messages_with_carry(session.baseline(), session.records(), session.carry());
messages.push(ChatMessage::User {
content: prompt.trim_end().to_string(),
});
let request = ChatRequest {
slot: cfg.slots.background,
messages,
tools: Vec::new(),
thinking: true,
};
match client.chat_with_retry(&request, &mut |_| {}) {
Ok(completion) => {
let text = completion.content.unwrap_or_default();
let text = neutralise(&text);
Ok(cap_chars(text.as_ref(), cfg.compact.summary_chars))
}
Err(e) if is_context_full(&e) => {
Ok(fallback_summary(session.records(), cfg.compact.summary_chars))
}
Err(e) => Err(CompactError::Failed {
detail: e.to_string(),
}),
}
}
Steps
-
1. Copy the given test.
cp docs/plans/M5d1/files/crates/loopd/tests/compact_summary.rs crates/loopd/tests/ -
2. See it fail.
cargo build --tests --keep-going -p loopd, timeout 600000 ms: test targetcompact_summarydoes not compile (E0432unresolved importloopd::compact::CompactErrorand/orE0599no functionsummarise). Every other target compiles.compact_duestill compiles. -
3. The edits. E1, E2.
cargo check -p loopdprints no error and no warning.wc -l crates/loopd/src/compact.rsprints a number ≤ 500. -
4. The tests.
cargo test -p loopd --test compact_summary --test compact_due: every test passes, 6 incompact_summaryand 6 incompact_due. -
5. The runbook. From the repository root:
{ echo; cat docs/plans/M5d1/files/runbook-session-compact-failed.md; } >> docs/runbook.mdThen
grep -c '^## session-compact-failed$' docs/runbook.mdprints1. -
6. Check.
git diff --statlistscrates/loopd/src/compact.rsanddocs/runbook.md.git status --shortshowscompact_summary.rsas new. -
7. Run the gate.
cargo fmt --all, thenmake gate, timeout 600000 ms. Last linegate: ok. Test count 1495 (1489 before, plus 6). -
8. Log and commit. Task cell
M5d1/06-loopd-compact-summary. Stage withgit add crates/loopd docs/runbook.md docs/implementer-log.md Cargo.lock, then commit asAGENTS.mdsays.
Done when
make gateprintsgate: okwith 1495 tests.git diff HEAD~1 --statlists onlycrates/loopd/src/compact.rs,crates/loopd/tests/compact_summary.rs,docs/runbook.mdand the log.
Stop and report if
- The branch check fails; a "before" block is not found exactly once; step 2 differs;
cargo checkwarns; a test fails; the gate count is not 1495; a comment does not fit its code;compact.rsexceeds 500 lines; you add a fallback for a missingcompact.md; you send tools on the summary request; you useslots.mainfor the summary.
What the given tests pin
The summary request is id_slot of the background slot, omits tools, has thinking on, and its
last user is compact.md after trim_end; the completion is capped to summary_chars; a
context-full summary is the epoch's User texts; tags in the completion are neutralised; a
missing compact.md names the file and the runbook; a non-context-full infer error is
CompactError::Failed with the same pointer.