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
+4
View File
@@ -8,6 +8,8 @@ Unix sockets. You are implementing it one task at a time.
1. The owner gives you one task file, `docs/plans/M<n>/NN-name.md`. Read it fully. Do that task and 1. The owner gives you one task file, `docs/plans/M<n>/NN-name.md`. Read it fully. Do that task and
nothing else. Do not start the next task. nothing else. Do not start the next task.
2. Do the steps in order. Where a step shows a command and its expected output, run it and compare. 2. Do the steps in order. Where a step shows a command and its expected output, run it and compare.
The first step is almost always a `cp`. Run that command before you Read any crate source.
Do not `ls` a tests directory. Do not read `tests/support/`.
3. Do not read `docs/design.md` or other plans unless the task tells you to. The task file quotes 3. Do not read `docs/design.md` or other plans unless the task tells you to. The task file quotes
what you need. Do not read `docs/implementer-log.md` until the log-and-commit step, and then what you need. Do not read `docs/implementer-log.md` until the log-and-commit step, and then
only the first 12 lines (the title through the header row). The rest of that file will fill only the first 12 lines (the title through the header row). The rest of that file will fill
@@ -72,6 +74,8 @@ These come from defects found in review. The evidence is in `docs/implementer-le
- Report every problem you find, not only the first. - Report every problem you find, not only the first.
- Log every attempt, including one you abandon. - Log every attempt, including one you abandon.
- Never end a turn by describing what you are about to do. Do it, then report. - Never end a turn by describing what you are about to do. Do it, then report.
- Run the task's step 1 (the `cp`) before you Read crate source. Do not `ls` a tests directory.
Do not read `tests/support/`. The facts table is the API.
- A refused or failed tool call is not a reason to end your turn. Read the error, then continue - A refused or failed tool call is not a reason to end your turn. Read the error, then continue
with another tool call. with another tool call.
- Fill `todo!()` bodies one at a time, and run `cargo check` after each. - Fill `todo!()` bodies one at a time, and run `cargo check` after each.
+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. | | 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. | | 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. | | 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 ## 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` **Commit subject:** `loopd: heartbeat tick on the background slot`
**Log row:** the Task cell is `M5d2/06-loopd-scheduler-heartbeat`. **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 ## Goal
A `Scheduler` whose `tick(&Context, now)` runs the standing `heartbeat` session when 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`. `crates/loopd/src/main.rs`, `docs/implementer-log.md`.
- Do not touch: any other file. - Do not touch: any other file.
**Read only what the step names.** Step 1 is the `cp`. Do not open `docs/implementer-log.md`, At step 7, read only the first 12 lines of `docs/implementer-log.md`. The rest of that file
`docs/runbook.md`, or `crates/loopd/src/{turn,session,compact,baseline,config}.rs` to start. fills the session (~360 kB).
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).
## Facts you need ## 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. That sleep is production only. Tests never sleep.
- **`channel.rs` stays ≤ 500 lines.** `hold` is about 15 lines. - **`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. - The items the edits use. This table is complete.
| Item | What it does | | Item | Signature |
|---|---| |---|---|
| `Session::slot` / `create` / `open` | standing session | | `Session::open` | `open(home: &Path, id: SessionId) -> Result<Session, SessionError>` |
| `run_turn` / `finish_turn` | the pulse | | `Session::create` | `create(home, id, baseline, slot: u32) -> Result<Session, SessionError>` |
| `Baseline::assemble` | new session | | `SessionError::NotFound` | `_` |
| `jobs::close_leftovers` / `live_session` / `append` | leftovers | | `Baseline::assemble` | `assemble(&ctx.cfg, &ctx.registry) -> Result<Baseline, _>` |
| `Timestamp::unix_millis` / `to_rfc3339` | due and the time line | | `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 ## The edits, exactly
@@ -134,8 +149,9 @@ pub struct Busy;
### `crates/loopd/src/scheduler.rs` ### `crates/loopd/src/scheduler.rs`
Write this module. Fill `tick`. Cron jobs stay a comment until task 07: after heartbeat, do Write this module. `tick` is already glue. Fill `heartbeat` then `close_idle_leftovers`,
not iterate job files yet. `cargo check -p loopd` after each (T25). Do not iterate job files (task 07). Do not add
`run_jobs`.
```rust ```rust
//! Isolated heartbeat (and, from task 07, cron) turns (M5d spec, section 4). //! Isolated heartbeat (and, from task 07, cron) turns (M5d spec, section 4).
@@ -170,15 +186,27 @@ impl Scheduler {
} }
pub fn tick(&mut self, ctx: &Context, now: Timestamp) -> Vec<TickWork> { pub fn tick(&mut self, ctx: &Context, now: Timestamp) -> Vec<TickWork> {
let _ = (ctx, now); let mut work = Vec::new();
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!()
}
}
fn close_idle_leftovers(ctx: &Context, now: Timestamp) {
let _ = (ctx, now);
todo!()
} }
``` ```
Replace the `tick` body in this task. `leftovers_closed` is set true after the first leftover
pass so it runs once.
## Steps ## Steps
- [ ] **1. Copy the given test.** - [ ] **1. Copy the given test.**
@@ -190,9 +218,10 @@ pass so it runs once.
- [ ] **2. See it fail.** `cargo build --tests --keep-going -p loopd`, timeout 600000 ms: test - [ ] **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`, target `scheduler_heartbeat` does not compile (`E0432` unresolved `loopd::scheduler`,
`E0599` no method `hold`). Every other target compiles. `E0599` no method `hold`). Every other target compiles.
- [ ] **3. The edits.** C1, C2, `scheduler.rs`, `pub mod scheduler;`, main thread. Fill - [ ] **3. The edits.** C1, C2, `scheduler.rs` (fill `heartbeat` then `close_idle_leftovers`,
`tick`. `cargo check -p loopd` prints no error and no warning. `cargo check -p loopd` after each), `pub mod scheduler;`, main thread. `cargo check -p loopd`
`wc -l crates/loopd/src/scheduler.rs` and `wc -l crates/loopd/src/channel.rs` each ≤ 500. 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 - [ ] **4. The tests.** `cargo test -p loopd --test scheduler_heartbeat --test channel --test
session_slot`: every test passes, 6 in `scheduler_heartbeat`. session_slot`: every test passes, 6 in `scheduler_heartbeat`.
- [ ] **5. The runbook.** - [ ] **5. The runbook.**
@@ -5,6 +5,10 @@ is empty; otherwise stop. Never switch branch.
**Commit subject:** `loopd: cron tick, new session per run` **Commit subject:** `loopd: cron tick, new session per run`
**Log row:** the Task cell is `M5d2/07-loopd-scheduler-cron`. **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 ## Goal
After the heartbeat work, `tick` loads `{home}/jobs/*.toml` and runs each job whose schedule After the heartbeat work, `tick` loads `{home}/jobs/*.toml` and runs each job whose schedule
+1 -1
View File
@@ -94,7 +94,7 @@ for task in "$plan"/[0-9][0-9]-*.md; do
number=$(echo "$name" | cut -c1-2) number=$(echo "$name" | cut -c1-2)
[ "$number" -ge "$first" ] || continue [ "$number" -ge "$first" ] || continue
row="Your log row's Task cell is \`$milestone/$name\`, and you are $agent: write $agent in its Model column, and end the commit message with this line, exactly: $trailer" row="Your log row's Task cell is \`$milestone/$name\`, and you are $agent: write $agent in its Model column, and end the commit message with this line, exactly: $trailer"
prompt="Read \`$task\` and do exactly that task. $row" prompt="Read \`$task\` and do exactly that task. Do its steps in order. Run step 1 (usually a cp) before you Read any crate source, tests/support, or docs/implementer-log.md. $row"
if [ "${DRY_RUN:-0}" = 1 ]; then if [ "${DRY_RUN:-0}" = 1 ]; then
echo "opencode run --agent implement -m $model ${OPENCODE_FLAGS:-} --title $milestone/$name \"$prompt\"" echo "opencode run --agent implement -m $model ${OPENCODE_FLAGS:-} --title $milestone/$name \"$prompt\""