docs: M5d-2 task 06: cp first, tick is glue

The second 06 session listed tests/, read eight loopd modules and
support/mod.rs, grepped bxcore, then hit Context size exceeded with
no commit. The cp is now the first action; tick is glue over two
todo!() bodies; run-plan tells the session to run step 1 before
reading crate source.

Co-Authored-By: Grok 4.6 <noreply@x.ai>
This commit is contained in:
2026-10-03 16:04:38 -07:00
co-authored by Grok 4.6
parent 2eb0ffd6d7
commit 4dd3b87db3
5 changed files with 61 additions and 23 deletions
+2 -1
View File
@@ -101,7 +101,8 @@ How it is used:
| T59 | After `gate: ok`, the task is done counting. Do not tell the implementer to run `cargo test --workspace` (or the gate) again only to compare a number. AGENTS.md already forbids a second gate "only to count"; say the same of the workspace test run. | M5c-2: all twelve sessions ran `cargo test --workspace` after a passing gate to rediscover the total, even with T50's wording in the task. |
| T60 | The driver must not truncate a task's run log, and must not start a task whose commit and `done` row are already in HEAD. A power-loss resume continues the existing log file; it does not open it for write from offset zero, and it skips a task that is already committed. | M5c-2 task 05: `.state/runs/M5c2/05-memoryd-honcho-readers.log` is four `=== …` resume sections after HEAD was at task 12; the original transcript is gone. Commit `b370c60` was already complete. |
| T61 | Name where the log row goes: immediately under the header, newest first. The implementer otherwise inserts by date among older rows. | M5d-1 task 06. |
| T62 | The implementer log is hundreds of kilobytes. A task that needs a row must say to read only the first 12 lines (title through the header). Reading the whole file exceeds the session context and the driver reports "made no commit". | M5d-2 task 06: after reading the task and supporting modules it opened `docs/implementer-log.md` and OpenCode returned `Context size has been exceeded.`; HEAD did not move. |
| T62 | The implementer log is hundreds of kilobytes. A task that needs a row must say to read only the first 12 lines (title through the header). Reading the whole file exceeds the session context and the driver reports "made no commit". | M5d-2 task 06: after reading the task and supporting modules it opened `docs/implementer-log.md` and OpenCode returned `Context size has been exceeded.`; HEAD did not move. Second run: it skipped the log, listed `tests/`, read `session`/`turn`/`compact`/`config`/`jobs`/`baseline`/`support/mod.rs`, grepped `bxcore`, then the same error on `ids.rs`. |
| T63 | Put the task's `cp` above the Goal, name the crate files they must not Read, and give `tick` as glue over small `todo!()` bodies (T25). A facts table is not enough: Ornith still opens those files and fills the session before step 1. | Same two M5d-2 task 06 runs. |
## What worked and should be kept
+50 -21
View File
@@ -5,6 +5,14 @@ is empty; otherwise stop. Never switch branch.
**Commit subject:** `loopd: heartbeat tick on the background slot`
**Log row:** the Task cell is `M5d2/06-loopd-scheduler-heartbeat`.
**First action.** Run this before any Read of crate source. Do not `ls` `crates/loopd/tests/`.
Do not Read `tests/support/`, `session.rs`, `turn.rs`, `compact.rs`, `config.rs`, `jobs.rs`,
`baseline.rs`, `bxcore`, or `docs/implementer-log.md`. The signatures below are complete.
```sh
cp docs/plans/M5d2/files/crates/loopd/tests/scheduler_heartbeat.rs crates/loopd/tests/
```
## Goal
A `Scheduler` whose `tick(&Context, now)` runs the standing `heartbeat` session when
@@ -38,11 +46,8 @@ Tests call `tick(ctx, now)` with injected time. No sleeps (T27).
`crates/loopd/src/main.rs`, `docs/implementer-log.md`.
- Do not touch: any other file.
**Read only what the step names.** Step 1 is the `cp`. Do not open `docs/implementer-log.md`,
`docs/runbook.md`, or `crates/loopd/src/{turn,session,compact,baseline,config}.rs` to start.
The facts table is complete. At step 7, read only the first 12 lines of
`docs/implementer-log.md` (title through the header row) and insert the new row immediately
under the header. The rest of that file fills the session (~360 kB).
At step 7, read only the first 12 lines of `docs/implementer-log.md`. The rest of that file
fills the session (~360 kB).
## Facts you need
@@ -93,15 +98,25 @@ under the header. The rest of that file fills the session (~360 kB).
That sleep is production only. Tests never sleep.
- **`channel.rs` stays ≤ 500 lines.** `hold` is about 15 lines.
- **Uses.** The skeleton compiles with only `Context` and `Timestamp`. When filling `heartbeat`,
add `use std::fs`, `Baseline`, `compact`, `Session`/`SessionError`, `Runtime`/`run_turn`,
`TurnEvent`. When filling leftovers, add `jobs::{self, Run, RunStatus}`.
- The items the edits use. This table is complete.
| Item | What it does |
| Item | Signature |
|---|---|
| `Session::slot` / `create` / `open` | standing session |
| `run_turn` / `finish_turn` | the pulse |
| `Baseline::assemble` | new session |
| `jobs::close_leftovers` / `live_session` / `append` | leftovers |
| `Timestamp::unix_millis` / `to_rfc3339` | due and the time line |
| `Session::open` | `open(home: &Path, id: SessionId) -> Result<Session, SessionError>` |
| `Session::create` | `create(home, id, baseline, slot: u32) -> Result<Session, SessionError>` |
| `SessionError::NotFound` | `_` |
| `Baseline::assemble` | `assemble(&ctx.cfg, &ctx.registry) -> Result<Baseline, _>` |
| `Runtime` | `{ cfg: &ctx.cfg, client: &ctx.client, port: &*ctx.port, registry: &ctx.registry }` |
| `run_turn` | `run_turn(&mut session, &runtime, &text, None, &mut emit)` |
| `compact::finish_turn` | `finish_turn(&mut session, &runtime, &*ctx.memory, &text, None, &mut emit, outcome)` |
| `jobs::load` | `load(dir: &Path) -> Result<Vec<Job>, JobsError>` |
| `jobs::live_session` | `live_session(ledger, name) -> Result<Option<SessionId>, _>` |
| `jobs::append` | `append(ledger, &Run { time, name, session, status })` |
| `RunStatus::Failed` | leftover close |
| `Timestamp` | `unix_millis() -> u64`, `to_rfc3339() -> String`, `now()` |
## The edits, exactly
@@ -134,8 +149,9 @@ pub struct Busy;
### `crates/loopd/src/scheduler.rs`
Write this module. Fill `tick`. Cron jobs stay a comment until task 07: after heartbeat, do
not iterate job files yet.
Write this module. `tick` is already glue. Fill `heartbeat` then `close_idle_leftovers`,
`cargo check -p loopd` after each (T25). Do not iterate job files (task 07). Do not add
`run_jobs`.
```rust
//! Isolated heartbeat (and, from task 07, cron) turns (M5d spec, section 4).
@@ -170,14 +186,26 @@ impl Scheduler {
}
pub fn tick(&mut self, ctx: &Context, now: Timestamp) -> Vec<TickWork> {
let _ = (ctx, now);
Vec::new()
let mut work = Vec::new();
if !self.leftovers_closed {
close_idle_leftovers(ctx, now);
self.leftovers_closed = true;
}
self.heartbeat(ctx, now, &mut work);
work
}
fn heartbeat(&mut self, ctx: &Context, now: Timestamp, work: &mut Vec<TickWork>) {
let _ = (ctx, now, work);
todo!()
}
}
```
Replace the `tick` body in this task. `leftovers_closed` is set true after the first leftover
pass so it runs once.
fn close_idle_leftovers(ctx: &Context, now: Timestamp) {
let _ = (ctx, now);
todo!()
}
```
## Steps
@@ -190,9 +218,10 @@ pass so it runs once.
- [ ] **2. See it fail.** `cargo build --tests --keep-going -p loopd`, timeout 600000 ms: test
target `scheduler_heartbeat` does not compile (`E0432` unresolved `loopd::scheduler`,
`E0599` no method `hold`). Every other target compiles.
- [ ] **3. The edits.** C1, C2, `scheduler.rs`, `pub mod scheduler;`, main thread. Fill
`tick`. `cargo check -p loopd` prints no error and no warning.
`wc -l crates/loopd/src/scheduler.rs` and `wc -l crates/loopd/src/channel.rs` each ≤ 500.
- [ ] **3. The edits.** C1, C2, `scheduler.rs` (fill `heartbeat` then `close_idle_leftovers`,
`cargo check -p loopd` after each), `pub mod scheduler;`, main thread. `cargo check -p loopd`
prints no error and no warning. `wc -l crates/loopd/src/scheduler.rs` and
`wc -l crates/loopd/src/channel.rs` each ≤ 500.
- [ ] **4. The tests.** `cargo test -p loopd --test scheduler_heartbeat --test channel --test
session_slot`: every test passes, 6 in `scheduler_heartbeat`.
- [ ] **5. The runbook.**
@@ -5,6 +5,10 @@ is empty; otherwise stop. Never switch branch.
**Commit subject:** `loopd: cron tick, new session per run`
**Log row:** the Task cell is `M5d2/07-loopd-scheduler-cron`.
**First action.** Copy the given test before any other Read of crate source. Do not `ls`
`crates/loopd/tests/`. Do not Read `tests/support/` or `docs/implementer-log.md` (except the
first 12 lines at the log step).
## Goal
After the heartbeat work, `tick` loads `{home}/jobs/*.toml` and runs each job whose schedule