docs: M5d-1 and M5d-2 implementation plans for Ornith

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>
This commit is contained in:
2026-10-02 21:54:37 -07:00
co-authored by Grok 4.6
parent 069cd1686a
commit 3726ae585f
48 changed files with 7319 additions and 0 deletions
+130
View File
@@ -0,0 +1,130 @@
# 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 **1489** (1483 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 1489 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 1489; 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.