Files
boxmaker/docs/plans/M5d1/05-loopd-compact-due.md
T
kyleandGrok 4.6 c3b05cf003 docs: M5d-1 checklist fixes before hand-over
Given tests: Action::Turn (not To), send_turn of epoch 1, clippy on
compact_summary, jsonl-without-baseline, live turn after compact sends
the carry. send_turn maps Damaged to Local("log") as read_turn did.
Gate counts 1484 → 1508. Mutations recorded in the README.

Co-Authored-By: Grok 4.6 <noreply@x.ai>
2026-10-02 22:36:51 -07:00

131 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# M5d-1 task 05: when to compact, and the fallback summary
**Branch:** `m5d1`. Check it: `git branch --show-current` prints `m5d1` and `git status --short`
is empty; otherwise stop. Never switch branch.
**Commit subject:** `loopd: compaction threshold and the fallback summary`
**Log row:** the Task cell is `M5d1/05-loopd-compact-due`.
## Goal
A session is due to compact when the current epoch's last `Usage` has
`cache_n + prompt_n` at or above `threshold × expect.n_ctx` (spec 3.3). There is no idle timer.
If the summary call is context-full (task 06), the fallback is the epoch's `User` texts, newest
last, cut to `summary_chars` on a character boundary (spec 3.4).
This task is those two pure functions, the `compact` module, and the starter `config/compact.md`.
No inference, no disk besides copying the starter.
## Every place that changes
| Place | What happens there |
|---|---|
| `crates/loopd/src/compact.rs` | new: `due`, `fallback_summary`, `cap_chars` |
| `crates/loopd/src/lib.rs` | `pub mod compact;` |
| `config/compact.md` | new, given starter |
| `crates/loopd/tests/compact_due.rs` | new, given |
| everything else | **no change** |
## Files
- Copy, from `docs/plans/M5d1/files/` (the same path under it): the new test
`crates/loopd/tests/compact_due.rs` and the starter `config/compact.md`.
- Create: `crates/loopd/src/compact.rs` (skeleton below).
- Modify: `crates/loopd/src/lib.rs`, `docs/implementer-log.md`.
- Do not touch: any other file.
## Facts you need
- **Depth is saturating.** `cache_n.saturating_add(prompt_n) as f64 >= threshold * (n_ctx as f64)`.
0.85 × 262144 is 222822.4, so 222822 is not due and 222823 is. 0.85 × 1000 is 850 exactly.
- **Cast.** `u64 as f64` is exact for these magnitudes (n_ctx is 262144). Do not use `as` on
untrusted indexes; these are our own config and the server's counts.
- **Fallback join.** `"\n\n"` between texts. Newest last: reverse the `User` contents, then join,
then cap.
- **Cap.** `s.chars().take(n).collect::<String>()`. `n == 0` is empty. A multi-byte character
is not split.
- **`config/compact.md`** is copied like `config/system.md`. Task 06 reads it from
`Config::compact_prompt`.
- The items the edits use. This table is complete.
| Item | What it does |
|---|---|
| `bxcore::Usage` | `cache_n`, `prompt_n` |
| `LogRecord::User { content, .. }` | fallback texts |
| `str::chars`, `Iterator::take` | the cap |
## Skeleton: `crates/loopd/src/compact.rs`
Write this file, then fill the two `todo!()` bodies from their comments and delete the
comments. Each comment is at most six lines.
```rust
//! 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};
/// True when `cache_n + prompt_n` is at or above `threshold * n_ctx`.
pub fn due(usage: &Usage, threshold: f64, n_ctx: u64) -> bool {
// saturating_add the two counts; compare as f64 to `threshold * (n_ctx as f64)`.
todo!()
}
/// The epoch's `User` texts, newest last, joined with `"\n\n"`, then `cap_chars`.
pub fn fallback_summary(records: &[LogRecord], summary_chars: usize) -> String {
// Collect `&str` contents of every `User`, reverse, join `"\n\n"`, then `cap_chars`.
todo!()
}
pub(crate) fn cap_chars(s: &str, n: usize) -> String {
s.chars().take(n).collect()
}
```
### `crates/loopd/src/lib.rs`
**L1.** After `pub mod channel;` add `pub mod compact;` (keep rustfmt's order; if rustfmt
moves the line, accept that).
## Steps
- [ ] **1. Copy the given files.**
```sh
cp docs/plans/M5d1/files/crates/loopd/tests/compact_due.rs crates/loopd/tests/
cp docs/plans/M5d1/files/config/compact.md config/compact.md
```
- [ ] **2. Write the skeleton** at `crates/loopd/src/compact.rs` and make L1. See it fail:
`cargo test -p loopd --test compact_due`, timeout 600000 ms: the two `due` tests and the
three `fallback` tests panic on `todo!()` (or fail their asserts if you filled the bodies
early). If the test target does not compile (`E0432` unresolved `loopd::compact`), the
`mod` line is missing.
- [ ] **3. Fill `due` and `fallback_summary`.** Delete each instruction comment. `cargo check
-p loopd` prints no error and no warning.
- [ ] **4. The tests.** `cargo test -p loopd --test compact_due`: 6 passed.
- [ ] **5. Check.** `git diff --stat` lists `crates/loopd/src/lib.rs`,
`crates/loopd/src/compact.rs`, `config/compact.md`. `git status --short` shows
`compact_due.rs` as new.
- [ ] **6. Run the gate.** `cargo fmt --all`, then `make gate`, timeout 600000 ms. Last line
`gate: ok`. Test count **1490** (1484 before, plus 6).
- [ ] **7. Log and commit.** Task cell `M5d1/05-loopd-compact-due`. Stage with
`git add crates/loopd config/compact.md docs/implementer-log.md Cargo.lock`, then commit
as `AGENTS.md` says.
## Done when
- `make gate` prints `gate: ok` with 1490 tests.
- `git diff HEAD~1 --stat` lists only `crates/loopd/src/lib.rs`, `crates/loopd/src/compact.rs`,
`crates/loopd/tests/compact_due.rs`, `config/compact.md` and the log.
## Stop and report if
- The branch check fails; step 2 fails to compile for a reason other than the missing module
or `todo!()`; a test fails; the gate count is not 1490; you add an idle timer. There is none.
## What the given tests pin
849/1000 is not due, 850 is; 222822/262144 is not due, 222823 is; threshold 1.0 is the full
window; fallback is newest-last with `"\n\n"`; the cap is in characters on a boundary; no
users is empty.