Files
kyleandClaude Fable 5.1 58c7738721 Review M2a: accept with two follow-up tasks; record models and lessons
The branch passes every check, including make verify-device on
straylight and repeated timing runs under load. Reading and probing
found that inferproxy does not pass an upstream close on to a client
that is still sending, and that the chunked body reader delivers a
stream only when the caller's buffer fills or the stream ends. Both
were also gaps in the tasks and tests, so tasks 14 and 15 carry the
fixes with new tests checked against the reference.

The Model column is corrected: tasks 04 to 06 and 08 to 13 were Ornith.
Lessons gain four implementer tips and five task-writing tips; three
rules are promoted to AGENTS.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 15:43:06 -07:00
..

M2a implementation plan: the inference path

For the implementing model: do not work from this file. The owner gives you one task file at a time (01-… to 15-…). This file is the index for the owner and the reviewer.

Goal: loopd can hold a correct, robust conversation with llama-server through a Unix socket: requests built from typed input, streams reassembled exactly, every kind of silence and failure ending in a defined way, and a self-test that refuses a server it does not recognise.

Architecture: inferproxy forwards bytes between infer.sock and the router. In loopd, a hand-written HTTP/1.1 client and SSE reader sit under a llama client that owns the waits, the liveness limit, the thinking cap, the per-slot gate and retry. Nothing in the client starts a thread; timers are socket read timeouts. Behaviour is pinned by tests that run against a scripted fake server replaying responses recorded from straylight.

Tech stack: Rust stable (edition 2024, rust-version = "1.95"), serde, serde_json, toml, emsha 1.0.4 (new), std only for sockets and threads.

Spec: docs/specs/2026-09-17-m2a-inference-path.md. Measurements: docs/inference-contract.md.

Global constraints

  • Everything in AGENTS.md, including "Lessons from earlier reviews".
  • No new dependency except emsha. No HTTP, SSE, async or randomness crate.
  • Library code never panics on what a peer sends. Everything read from a socket is bounded.
  • Our own formats reject unknown fields. The server's JSON does not: it sends fields we do not use and newer builds add more, so structs that parse the server's responses must not use deny_unknown_fields. Each task says which kind it is dealing with.
  • Branch m2a. One task, one fresh OpenCode session, one commit. Run cargo fmt --all before the gate. Review happened once, after task 13; tasks 14 and 15 are its follow-ups.

Tasks

# File Delivers Tests that define it
01 01-proto-sha256.md proto::sha256, Sha256, HashError over emsha proto/tests/hash.rs
02 02-inferproxy.md The forwarder, its two limits, its command line inferproxy/tests/bucket.rs, forward.rs
03 03-loopd-config.md loopd::config loopd/tests/config.rs
04 04-loopd-http.md loopd::http; brings in the fake server and all recordings loopd/tests/http.rs
05 05-loopd-sse.md loopd::sse loopd/tests/sse.rs
06 06-llama-request.md loopd::llama types and request::build_body loopd/tests/request.rs
07 07-llama-assemble.md llama::assemble::Assembler loopd/tests/assemble.rs
08 08-llama-info.md props, slots, tokenize, cache_outcome loopd/tests/info.rs
09 09-llama-chat.md Client::chat: waits, liveness, errors loopd/tests/chat.rs
10 10-llama-cap.md The thinking cap loopd/tests/cap.rs
11 11-llama-gate-retry.md SlotGate, chat_with_retry loopd/tests/retry.rs
12 12-selftest.md loopd::selftest, loopd selftest --config loopd/tests/selftest.rs
13 13-verify-device.md make verify-device against straylight loopd/tests/device.rs
14 14-inferproxy-close.md Review follow-up: inferproxy closes towards the client when the upstream closes updated inferproxy/tests/forward.rs
15 15-http-streaming.md Review follow-up: chunked body data is returned as soon as it is available updated loopd/tests/http.rs

files/ holds everything the tasks copy into place: tests, the fake server (loopd/tests/support/mod.rs), recordings (fixtures/http/*.http), expected results derived from the recordings by a separate script (fixtures/expected/), and the new Makefile.

The plan was checked the same way as M1: a private reference implementation passes every test, the gate passes after every task in order, the timing tests were run repeatedly under CPU load, and the reference passes make verify-device on straylight.

For the owner: running a task

In ~/src/boxmaker, start a fresh OpenCode session with Laguna S 2.1 and send:

Read docs/plans/M2a/01-proto-sha256.md and do exactly that task.

Then the next file in a new session. Or let tools/run-plan.sh docs/plans/M2a do that: it runs each task in a fresh opencode run session with Laguna, and stops at the first task that does not end with a commit, a clean tree and a done row. If a session ends with a stopped row in docs/implementer-log.md, do not start the next task. Task 13 talks to straylight: Ornith must be loadable and slot 0 should not be in heavy use while it runs.

For the reviewer: after task 13

  1. git log --oneline master..m2a: thirteen commits with the Implemented-By trailer.
  2. Copied files are unchanged: for f in $(cd docs/plans/M2a/files && find . -type f); do cmp "docs/plans/M2a/files/$f" "$f"; done
  3. git diff master..m2a --stat -- docs/design.md docs/specs docs/plans AGENTS.md CLAUDE.md deny.toml is empty.
  4. make gate, make audit, make verify-device.
  5. Read every source file against its task and the spec. Probe from outside with inputs the tests do not contain, especially: malformed HTTP, a server that misbehaves mid-stream, several threads on the gate, and limits at their boundaries.
  6. Run the timing tests repeatedly under CPU load.
  7. Write findings under "Reviews" in docs/implementer-log.md, and turn them into rows in docs/implementer-lessons.md, filling in "Seen again" for the M1 tips.