review: M5d-1 epochs and compaction

Co-Authored-By: Grok 4.6 <noreply@x.ai>
This commit is contained in:
2026-10-03 01:17:34 -07:00
co-authored by Grok 4.6
parent b274fa7d62
commit 48fac185ad
4 changed files with 74 additions and 7 deletions
+51 -1
View File
@@ -8,6 +8,7 @@ reviewer adds findings under "Reviews" once per milestone.
|---|---|---|---|---|---|---|---|
| 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/07-loopd-compact-advance | 2026-10-03 | done | 2 | pass | none | Copied the given test `crates/loopd/tests/compact_advance.rs` (new). Step 2 (`cargo build --tests --keep-going -p loopd`) showed exactly the named failure: `compact_advance` with E0425 (`cannot find function commit in module compact`); every other target compiled. Made S1 (`session.rs`: `begin_epoch` after `log_path`, in that order — writes `<n>.baseline.json` with `to_json`, `create_new` the `<n>.jsonl`, accumulates `prior_users`/`prior_untrusted` from the old `records` before clearing them, sets `epoch`/`baseline`/`carry`/empty `records`/the new `File`, then `persist` `SessionStart` with the live `slot`; does not reset `next_call` or `slot`). Made C1 (`compact.rs`: extended the `use crate::` group to `Baseline`/`MemorySource`/`Registry` and the bxcore group to `Epoch`/`Timestamp`, then added `commit` after `summarise` — skips the append and takes `next`/summary from a trailing `EpochEnd`, else appends `EpochEnd` with `Epoch(epoch.0.saturating_add(1))`, reads `memory.block()` as `Failed`, `Baseline::assemble_with(cfg, registry, &block)` as `Failed`, then `begin_epoch`). `cargo check -p loopd` clean, no error or warning. `wc -l crates/loopd/src/session.rs` prints 438 and `crates/loopd/src/compact.rs` prints 134, each ≤ 500. `cargo test -p loopd --test compact_advance --test session_epochs --test compact_summary` passes: 6 in `compact_advance`, 9 in `session_epochs`, 6 in `compact_summary`. `git diff --stat` lists only `session.rs` and `compact.rs`; `git status --short` shows `compact_advance.rs` as new. `make gate` printed `gate: ok` with 1502 tests (1496 before, plus the 6 of `compact_advance.rs`). No new dependency, so `Cargo.lock` unchanged. | ornith-1.5-35b-a3b |
| M5d1/06-loopd-compact-summary | 2026-10-03 | done | 1 | pass | none | Edited `compact.rs` E1 (module doc, added the `use`s for `neutralise`, `messages_with_carry`, `Config`, `ChatMessage`/`ChatRequest`/`Client`, `is_context_full`, and the `CompactError` enum plus its `Display`/`Error` impls whose `Display` ends with the `session-compact-failed` pointer) and E2 (added `summarise`: read `compact.md` with the path in the error, build the background-slot request with empty `tools` and thinking on, call `chat_with_retry`, neutralise-and-cap the completion, fall back only on a context-full 400, else `Failed`). Copied `tests/compact_summary.rs`, appended the runbook's `session-compact-failed` entry. `compact.rs` is 92 lines; `cargo check` clean, all 12 compact tests pass, `make gate` prints `gate: ok`, 1496 tests. | Ornith-1.5-35B-A3B |
| M5d1/05-loopd-compact-due | 2026-10-03 | done | 1 | pass | none | Copied the given test `crates/loopd/tests/compact_due.rs` and the starter `config/compact.md` from `docs/plans/M5d1/files/`. Created `crates/loopd/src/compact.rs`: `due` does `cache_n.saturating_add(prompt_n) as f64 >= threshold * (n_ctx as f64)`; `fallback_summary` `filter_map`s `LogRecord::User { content, .. }` to `&str`, reverses, joins `"\n\n"`, then `cap_chars`; `cap_chars` is the given `chars().take(n)`. Added `pub mod compact;` after `pub mod channel;` in `lib.rs` (rustfmt order, accepted). `cargo check -p loopd` clean, no error or warning. `cargo test -p loopd --test compact_due` passes all 6. `git diff --stat` lists only `lib.rs` and `compact.rs`; `git status --short` shows `compact_due.rs` and `compact.md` as new. `make gate` printed `gate: ok` with 1490 tests (1484 before, plus the 6 of `compact_due.rs`). No new dependency, so `Cargo.lock` unchanged. | ornith-1.5-35b-a3b |
| M5d1/04-memoryd-honcho-epochs | 2026-10-02 | done | 1 | pass | none | Copied the given test `crates/memoryd/tests/honcho_epochs.rs` (new). Step 2 (`cargo build --tests --keep-going -p memoryd`) showed exactly the named failure: `honcho_epochs` with one E0432 (`unresolved import memoryd::honcho::turns::read_session`); every other target compiled. Made H1 (`turns.rs`: `log_path` doc now names the sibling epoch files and that `read_session` concatenates them), H1b (inserted `epoch_logs` after `read_records`: lists `<n>.jsonl` under `sessions/<id>/`, `n` parsed as `u32`, sorted by `n`, a missing directory empty; then `read_session` concatenates `read_records` over each), H2 (`resume` reads `read_session` instead of `read_log(&log_path(...))`), H3 (`records` matches `read_session(...).map_err(|e| e.to_string())`, keeping the logged line shape); W1 (`worker.rs`: added `use ...decide::{final_reply, turn_records, user_text}` and `use ...turns::{LogError, TurnSync, log_path, read_session}`), W2 (`send_turn` loads `read_session`, `self.readable(&path)` on Ok, `LogError::Damaged(_)` -> `Unsent::Local("log")`, other error -> `Unsent::Unreadable(e.to_string())`, then `turn_records`/`user_text`/`final_reply` from `decide`), W3 (`batch` called with `text`/`reply` by value, no borrow). `read_turn` no longer used in the worker body but stays re-exported. `cargo check -p memoryd` clean, no error or warning. `wc -l` prints 369 for `turns.rs` and 453 for `worker.rs`, each ≤ 500. `cargo test -p memoryd --test honcho_epochs --test honcho_turns --test honcho_worker` passes: 6 in `honcho_epochs`, 16 in `honcho_turns`, 12 in `honcho_worker`. `git diff --stat` lists only `turns.rs` and `worker.rs`; `cargo fmt --all` changed nothing after the edits. `make gate` printed `gate: ok` with 1484 tests (1478 before, plus the 6 of `honcho_epochs.rs`). No new dependency, so `Cargo.lock` unchanged. | ornith-1.5-35b-a3b |
| M5d1/03-loopd-carry-messages | 2026-10-02 | done | 2 | pass | none | Copied the given test `crates/loopd/tests/carry_messages.rs` (new). Step 2 (`cargo build --tests --keep-going -p loopd`) showed exactly the named failure: `carry_messages` with one E0432 (`unresolved import loopd::baseline::{CARRY_HEADING, messages_with_carry}`); every other target compiled. Made B1 (`baseline.rs`: `CARRY_HEADING` = `concat!("Earlier ", "turns, summarised:")`, `messages` becomes `messages_with_carry(baseline, records, None)`, new `messages_with_carry` inserts the heading plus a blank line plus the summary as one `ChatMessage::User` after the system prompt and before walking `records`; the rest of the `match` untouched); T1 (`turn.rs`: `use crate::baseline::messages_with_carry`); T2 (`run_turn` passes `session.carry()`). `cargo check -p loopd` clean, no error or warning. `cargo test -p loopd --test carry_messages --test baseline --test turn` passes: 5 in `carry_messages`, 6 in `baseline`, 6 in `turn`. `git diff --stat` lists only `baseline.rs` and `turn.rs`; `cargo fmt --all` changed nothing. `make gate` printed `gate: ok` with 1478 tests (1473 before, plus the 5 of `carry_messages.rs`). No new dependency, so `Cargo.lock` unchanged. | ornith-1.5-35b-a3b |
@@ -94,7 +95,6 @@ reviewer adds findings under "Reviews" once per milestone.
| M4b/05-brokerd-config-gateway | 2026-09-24 | done | 1 | pass | none | Copied `docs/plans/M4b/files/crates/brokerd/tests/config_gateway.rs` and the new `support/rig.rs` (whose `Config` gained `gateway: None`) into `crates/brokerd/tests/`. In `config.rs`: added the `Gateway { socket: PathBuf }` struct (`#[serde(deny_unknown_fields, default)]`) before `default_podman`, the `gateway: Option<Gateway>` field (with `#[serde(default)]`) after `runner` in `Config`, and the `gateway_socket()` method before `audit_dir` — `None` when the section is absent, the default `<home>/run/gateway/gateway.sock` when the socket is empty, else the path as given. `cargo fmt --all` then `cargo test -p brokerd` green with `config_gateway` 4 passed; `make gate` prints `gate: ok` on the first run. | Ornith-1.5-35B-A3B |
| M4b/04-brokerd-answer-post | 2026-09-24 | done | 1 | pass | none | Moved `enum Answer` and `struct Answered` from `ledger.rs` into `approvals.rs` before `pub struct Entry` (with doc comments), added `pub use crate::approvals::{Answer, Answered};` next to the `Verdict` use and `DecisionRecord` to the `use proto` line in `approvals.rs`. Added the `post` field to `Answer::Approved`/`Answer::Refused`, threaded it through `ledger::answer` (step 3 binds `post`, step 6 writes it into `AuditEvent::Approval` instead of `None`), and gave the two `bxctl` answers in `admin.rs` `post: None`. Added `Table::contains` before `take`. Copied `ledger_post.rs`, `broker_pending.rs`, `broker_sequence.rs` and `ledger_answer.rs` into `crates/brokerd/tests/`; every suite passes, `ledger_post` 3 passed, `ledger.rs` is 487 lines, and `make gate` prints `gate: ok` on the first run. | Ornith-1.5-35B-A3B |
| M4b/03-bxctl-deny | 2026-09-24 | done | 1 | pass | none | Copied `docs/plans/M4b/files/crates/bxctl/tests/cli_deny.rs` into `crates/bxctl/tests/`. In `parse`, `"refuse" => parse_refuse(rest, home)` became `"refuse" | "deny" => parse_refuse(rest, home)`, so `deny` produces the same `Command::Refuse` as `refuse` for every argument list including the error cases. Added a blank line and the two-line `bxctl deny <id> [--reason TEXT] [--admin-socket PATH]` / `Another name for refuse.` block to `USAGE` after the `refuse` block, keeping the two-space and six-space indents. The copied test's `deny_parses_as_refuse` and `usage_names_deny` both failed before the changes and pass after; `cargo test -p bxctl` is green and `make gate` prints `gate: ok` on the first run. | Ornith-1.5-35B-A3B |
| M5d1/06-loopd-compact-summary | 2026-10-03 | done | 1 | pass | none | Edited `compact.rs` E1 (module doc, added the `use`s for `neutralise`, `messages_with_carry`, `Config`, `ChatMessage`/`ChatRequest`/`Client`, `is_context_full`, and the `CompactError` enum plus its `Display`/`Error` impls whose `Display` ends with the `session-compact-failed` pointer) and E2 (added `summarise`: read `compact.md` with the path in the error, build the background-slot request with empty `tools` and thinking on, call `chat_with_retry`, neutralise-and-cap the completion, fall back only on a context-full 400, else `Failed`). Copied `tests/compact_summary.rs`, appended the runbook's `session-compact-failed` entry. `compact.rs` is 92 lines; `cargo check` clean, all 12 compact tests pass, `make gate` prints `gate: ok`, 1496 tests. | Ornith-1.5-35B-A3B |
| M4b/02-proto-gateway-wire | 2026-09-24 | done | 2 | fail | none | Copied `gateway.rs`, `gateway_wire.rs` and the eight `gateway_*.json` fixtures into place; added `pub mod gateway;` after `pub mod frame;` and the re-export to `lib.rs`, the four `Message` variants plus the `use` to `wire.rs`, and the four `kind` arms (changing "fourteen" to "eighteen") in `broker.rs`. Filled `is_mattermost_id` (`id.len() == 26 && every byte a-z0-9`) and `ApprovalReply::check` in the given order: `by`, `post`, then a reason over `MAX_REASON` chars by `chars().count()`. All 3 `gateway_wire` tests pass; the first `make gate` failed on clippy `collapsible_if`, fixed by collapsing the nested `if let` into a let-chain guard, and the second prints `gate: ok`. | Ornith-1.5-35B-A3B |
| M4b/01-proto-escape | 2026-09-24 | done | 1 | pass | none | Copied `docs/plans/M4b/files/crates/proto/src/escape.rs`, `bxctl/src/escape.rs` and `proto/tests/escape.rs` into place, and added `pub mod escape;` to `proto/src/lib.rs` after `pub mod class;`. The bxctl file is now the one-line re-export `pub use proto::escape::{escape_json_text, escape_model_text};`, so no bxctl source or test changed and behaviour is identical. `cargo test -p proto --test escape` reports 8 passed; every bxctl suite passes unchanged (the moved escape test now runs against `proto::escape`); `cargo fmt --all` then `make gate` prints `gate: ok` on the first run. | Ornith-1.5-35B-A3B |
| M4a/16-gatewayd-secret-file-race | 2026-09-24 | done | 2 | pass | Reworded the template comment in `read_checked` to drop the literal `check_file` token, so step 5's grep prints only the credential branch's `std::fs::read(&path)` | Replaced `check_file` with `pub fn read_checked(path, between) -> Result<Zeroizing<Vec<u8>>, String>`: check the path (absolute; `symlink_metadata` as `named`; not a symlink; a regular file), call `between()`, open the path, take `opened` metadata, refuse if `(opened.dev(), opened.ino()) != (named.dev(), named.ino())`, then the owner-uid and mode checks on `opened` (not `named`), then read the whole file into a `Zeroizing<Vec<u8>>` from that same handle. Added `use std::io::Read;`. In `load`, the File branch now calls `read_checked(path, &|| {})` and `value(std::mem::take(&mut *bytes))` so nothing unwiped remains. The three suites pass (5, 8, 5); the only remaining `std::fs::read(` is the credential branch and `check_file` is gone. `make gate` prints `gate: ok`. | Ornith-1.5-35B-A3B |
@@ -1003,3 +1003,53 @@ markup (`ask_memory` stays at `minimal`); turn numbers restart per epoch; a `bxc
`sanitize::refuse`'s secret rules to replace `has_secret`; Mattermost usernames `a.b` and `a_b`
map to the same Honcho peer; queries from `secret` sessions go to Honcho on the assumption in
spec 8 and `docs/egress.md`.
### M5d-1, tasks 01 to 08 — reviewed 2026-10-03 by the design model (Grok 4.6)
Accepted. Tasks 01 to 08 by Ornith-1.5-35B-A3B through `tools/run-plan.sh` on this machine,
device-checked on straylight the same day. The branch differs from the reference
(`~/boxmaker-refs/m5d1-ref.bundle`, `e05845b`) in `crates/loopd/src/compact.rs` only: an equivalent
rewrite of `due` and `fallback_summary`, and a split of the import group. `docs/implementer-log.md`
differs because of the log rows. Every other product file matches.
| Check | Result |
|---|---|
| 8 task commits, each with `Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>` | pass |
| Given tests byte-identical to `docs/plans/M5d1/files/` (the runbook fragment is appended into `docs/runbook.md`) | pass |
| Protected files (`docs/design.md`, `docs/decisions.md`, `docs/inference-contract.md`, `docs/specs/`, `docs/plans/`, `AGENTS.md`, `CLAUDE.md`) untouched by Ornith | pass |
| Log rows | 8, all `done`; first gate pass in every row. Extra gate runs on 03 (2), 07 (2), 08 (3). Task 06's row was inserted after M4b/03; moved under the header in this review. Model cell on 06 is `Ornith-1.5-35B-A3B`; the trailer is `ornith-1.5-35b-a3b`. |
| No `unwrap`, `expect`, `panic!`, `#[allow]` or `unsafe` in new library code; every source file ≤ 500 lines; no new crate | pass |
| `make gate` on straylight (rustc 1.98.1 / clippy 0.1.98) | `gate: ok`, 1508 tests, the plan's count |
| Independent review of `git diff master m5d1 -- crates` against spec sections 3, 5, 7 and 8 | no product finding; `priors` walks `0..n`; carry is `ChatMessage::User` with `CARRY_HEADING` from `concat!`; summary on the background slot, empty tools, thinking on; `finish_turn` before `drop(held)`; Honcho concatenates every `<n>.jsonl`; `session-compact-failed` has a pointer and a runbook entry |
| # | Severity | Where | Finding | Resolution |
|---|---|---|---|---|
| 1 | process | implementer (tasks 03, 07, 08) | Extra `make gate` runs after a first-gate pass, only to count. | I18 seen again |
| 2 | process | implementer (task 06) | Log row inserted by date among M4b instead of immediately under the header. Model cell does not match the trailer. | Row relocated in this review; I19 / T61; AGENTS.md now says where the row goes |
Device check, 2026-10-03, on straylight (`target/debug` of `m5d1` at `b274fa7`; Ornith slot 0 live,
slot 2 background; slot 1 left busy; a private `$HOME`, production daemons untouched). `[compact]
threshold = 0.0` so every turn compacted. Empty `grants/` (memory tools are built-in). First run
failed the baseline check: the model recalled a planted tainted note on turn 1, tainting the
session, then wrote the teapot fact with `action: note` into `notes.md` marked untrusted, so
`memory.md` stayed empty and `1.baseline.json` lacked the fact. Compact, epoch-0 recall, and
`untrusted` persistence still worked. Second run wrote identity first (`memory` `add` `file=memory`),
then the tainted recall, then the epoch-0 phrase. Every item of the spec's section 7 for M5d-1
passed:
| Check | Result |
|---|---|
| Next baseline contains the `memory.md` fact after compact | Turn 1 stored `M5d1-check: the cobalt teapot lives in the attic.` and replied `Stored. magenta-walrus-epoch0`; `1.baseline.json` contains the fact; `TurnDone.untrusted` false |
| `recall` of an epoch-0 phrase still hits | Turn 3: `magenta-walrus-epoch0` hit the session log |
| `TurnDone.untrusted` stays true across the epoch change after a tainted recall | Turn 2 recalled `TAINTED-COBALT…`; `untrusted` true on turns 2 and 3; epochs 0–3 on disk |
What was good: eight tasks, first-gate pass in every row, no stop, no product bug against the
reference or the spec. Compaction, carry, Honcho concatenation and taint spanning epochs all held
on the live model.
Open items carried forward from M5c-2, with "turn numbers restart per epoch" closed by this half:
rift's dialectic at `reasoning: low` still returns raw tool-call markup (`ask_memory` stays at
`minimal`); a `bxcore` function for `sanitize::refuse`'s secret rules to replace `has_secret`;
Mattermost usernames `a.b` and `a_b` map to the same Honcho peer; queries from `secret` sessions
go to Honcho on the assumption in spec 8 and `docs/egress.md`. Known M5d-1 limits, left for
M5d-2: live turns still use `slots.main`; `[compact] summarise` is unused; no idle compact path.