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>
186 lines
6.7 KiB
Markdown
186 lines
6.7 KiB
Markdown
# M5d-1 task 03: the carried summary in the next epoch's prefix
|
|
|
|
**Branch:** `m5d1`. Check it: `git branch --show-current` prints `m5d1` and `git status --short`
|
|
is empty; otherwise stop. Never switch branch.
|
|
**Commit subject:** `loopd: replay a carried summary as an unlogged user message`
|
|
**Log row:** the Task cell is `M5d1/03-loopd-carry-messages`.
|
|
|
|
## Goal
|
|
|
|
For epoch `n > 0`, the request prefix is the new system message, then one `ChatMessage::User`
|
|
whose content is a fixed heading plus the carry, then the new epoch's records (spec 3.5). That
|
|
user message is **not** a `LogRecord::User`: it is not a turn, not reported to Honcho, and not
|
|
counted by `turns()`. Epoch 0 is unchanged (`carry` is `None`).
|
|
|
|
`run_turn` must send this prefix, or a compacted session's next live turn would omit the
|
|
summary.
|
|
|
|
## Every place that changes
|
|
|
|
| Place | What happens there |
|
|
|---|---|
|
|
| `crates/loopd/src/baseline.rs` | `CARRY_HEADING`; `messages_with_carry`; `messages` calls it with `None` |
|
|
| `crates/loopd/src/turn.rs` | the request uses `messages_with_carry` and `session.carry()` |
|
|
| `crates/loopd/tests/carry_messages.rs` | new, given |
|
|
| everything else | **no change**: not the existing `baseline` tests (they still call `messages`) |
|
|
|
|
## Files
|
|
|
|
- Copy, from `docs/plans/M5d1/files/` (the same path under it): the new test
|
|
`crates/loopd/tests/carry_messages.rs`.
|
|
- Modify: `crates/loopd/src/baseline.rs`, `crates/loopd/src/turn.rs`,
|
|
`docs/implementer-log.md`.
|
|
- Do not touch: any other file.
|
|
|
|
## Facts you need
|
|
|
|
- **The heading** is built from pieces so no file holds a chat-template tag whole (T33):
|
|
|
|
```rust
|
|
pub const CARRY_HEADING: &str = concat!("Earlier ", "turns, summarised:");
|
|
```
|
|
|
|
The user content is `format!("{CARRY_HEADING}\n\n{carry}")` when `carry` is `Some`. `None`
|
|
inserts nothing.
|
|
- **`messages` stays.** Existing tests and `limits.rs` call `messages(baseline, records)`. Make
|
|
it `messages_with_carry(baseline, records, None)` so those tests do not change.
|
|
- **`run_turn` today** (one place):
|
|
|
|
```rust
|
|
messages: messages(baseline, session.records()),
|
|
```
|
|
|
|
It must pass `session.carry()`. `Session::carry` is `Option<&str>` from task 02.
|
|
- Insert the carry user **after** the system message and **before** walking `records`.
|
|
- The items the edits use. This table is complete.
|
|
|
|
| Item | What it does |
|
|
|---|---|
|
|
| `concat!` | heading from pieces |
|
|
| `Session::carry(&self) -> Option<&str>` | task 02 |
|
|
| `ChatMessage::User { content }` | the carry, and log users |
|
|
|
|
## The edits, exactly
|
|
|
|
### `crates/loopd/src/baseline.rs`
|
|
|
|
**B1.** After the `Baseline` impl, replace `messages`. The lines
|
|
|
|
```rust
|
|
/// The system message, then each log record replayed in order. Every record variant is named, so a
|
|
/// new one is a compile error rather than a silent skip.
|
|
pub fn messages(baseline: &Baseline, records: &[LogRecord]) -> Vec<ChatMessage> {
|
|
let mut out = vec![ChatMessage::System {
|
|
content: baseline.system.clone(),
|
|
}];
|
|
for record in records {
|
|
```
|
|
|
|
become
|
|
|
|
```rust
|
|
/// The heading in front of a carried summary. Built from pieces (T33).
|
|
pub const CARRY_HEADING: &str = concat!("Earlier ", "turns, summarised:");
|
|
|
|
/// The system message, then each log record replayed in order. Every record variant is named, so a
|
|
/// new one is a compile error rather than a silent skip.
|
|
pub fn messages(baseline: &Baseline, records: &[LogRecord]) -> Vec<ChatMessage> {
|
|
messages_with_carry(baseline, records, None)
|
|
}
|
|
|
|
/// Like `messages`, and when `carry` is `Some` inserts one user message after the system
|
|
/// prompt: the heading, a blank line, the summary. That user is not a `LogRecord::User`.
|
|
pub fn messages_with_carry(
|
|
baseline: &Baseline,
|
|
records: &[LogRecord],
|
|
carry: Option<&str>,
|
|
) -> Vec<ChatMessage> {
|
|
let mut out = vec![ChatMessage::System {
|
|
content: baseline.system.clone(),
|
|
}];
|
|
if let Some(summary) = carry {
|
|
out.push(ChatMessage::User {
|
|
content: format!("{CARRY_HEADING}\n\n{summary}"),
|
|
});
|
|
}
|
|
for record in records {
|
|
```
|
|
|
|
The rest of the `match` is unchanged; do not touch it.
|
|
|
|
### `crates/loopd/src/turn.rs`
|
|
|
|
**T1.** The `use` line
|
|
|
|
```rust
|
|
use crate::baseline::messages;
|
|
```
|
|
|
|
becomes
|
|
|
|
```rust
|
|
use crate::baseline::messages_with_carry;
|
|
```
|
|
|
|
**T2.** The lines
|
|
|
|
```rust
|
|
let request = ChatRequest {
|
|
slot: rt.cfg.slots.main,
|
|
messages: messages(baseline, session.records()),
|
|
tools: baseline.tools.clone(),
|
|
thinking: true,
|
|
};
|
|
```
|
|
|
|
become
|
|
|
|
```rust
|
|
let request = ChatRequest {
|
|
slot: rt.cfg.slots.main,
|
|
messages: messages_with_carry(baseline, session.records(), session.carry()),
|
|
tools: baseline.tools.clone(),
|
|
thinking: true,
|
|
};
|
|
```
|
|
|
|
## Steps
|
|
|
|
- [ ] **1. Copy the given test.**
|
|
|
|
```sh
|
|
cp docs/plans/M5d1/files/crates/loopd/tests/carry_messages.rs crates/loopd/tests/
|
|
```
|
|
|
|
- [ ] **2. See it fail.** `cargo build --tests --keep-going -p loopd`, timeout 600000 ms: test
|
|
target `carry_messages` does not compile (`E0432` unresolved import
|
|
`loopd::baseline::{CARRY_HEADING, messages_with_carry}`). Every other target compiles.
|
|
- [ ] **3. The edits.** B1, T1, T2. `cargo check -p loopd` prints no error and no warning.
|
|
- [ ] **4. The tests.** `cargo test -p loopd --test carry_messages --test baseline --test turn`:
|
|
every test passes, 5 in `carry_messages` and the others as before.
|
|
- [ ] **5. Check.** `git diff --stat` lists exactly `crates/loopd/src/baseline.rs` and
|
|
`crates/loopd/src/turn.rs`.
|
|
- [ ] **6. Run the gate.** `cargo fmt --all`, then `make gate`, timeout 600000 ms. Last line
|
|
`gate: ok`. Test count **1478** (1473 before, plus 5).
|
|
- [ ] **7. Log and commit.** Task cell `M5d1/03-loopd-carry-messages`. Stage with
|
|
`git add crates/loopd docs/implementer-log.md Cargo.lock`, then commit as `AGENTS.md` says.
|
|
|
|
## Done when
|
|
|
|
- `make gate` prints `gate: ok` with 1478 tests.
|
|
- `git diff HEAD~1 --stat` lists only `crates/loopd/src/baseline.rs`, `crates/loopd/src/turn.rs`,
|
|
`crates/loopd/tests/carry_messages.rs` and `docs/implementer-log.md`.
|
|
|
|
## Stop and report if
|
|
|
|
- The branch check fails; a "before" block is not found exactly once; step 2 differs; `cargo
|
|
check` warns; a test fails; the gate count is not 1478; a comment does not fit its code.
|
|
- You think `messages` should insert the carry. It must not: existing tests call `messages` and
|
|
would see an extra user. `run_turn` is the one call that must pass `session.carry()`.
|
|
|
|
## What the given tests pin
|
|
|
|
Epoch 0 with `None` matches `messages`; a carry is a user after the system prompt and before the
|
|
epoch's users; the carry is not in the log records; the heading is the `concat!` pieces; the
|
|
first request of epoch 1 is not a prefix-extension of epoch 0's messages.
|