Files
boxmaker/docs/implementer-log.md
T
kyle 3d876d4fa9 Add the assembler that folds streamed chunks into a completion
Implemented-By: GLM-5.3 (z.ai, default settings) via OpenCode
2026-09-18 02:56:31 -07:00

14 KiB

Implementer log

Kept by the implementing model, one row per task. The column meanings are in AGENTS.md. The reviewer adds findings under "Reviews" once per milestone.

Task Date Status Gate runs First gate Deviations Notes
M1/01-workspace-and-gate 2026-09-17 done 1 pass none Crate skeletons, Cargo files and the given Makefile/deny.toml/test-gate-scripts.sh were already present untracked from a prior attempt; I verified them against the plan and created only the missing gate scripts, dependencies.md, egress.md and this log row.
M1/02-proto-values 2026-09-17 done 1 pass none Implemented ValueError, SessionId, Epoch, CallId, Hash32 and Timestamp in crates/proto/src/ids.rs and DataClass in class.rs, using serde try_from/into for string-backed JSON validation, a hand-written hex encoder and humantime for RFC 3339 parsing with canonical re-serialization.
M1/03-proto-wire 2026-09-17 done 2 pass none Added Envelope, Message, WireError, ErrorCode, ToolRequest, ToolResponse and DenyReason in crates/proto/src/wire.rs, re-exported from lib.rs; all 9 fixture tests pass and make gate prints gate: ok.
M1/04-proto-frame 2026-09-17 done 2 fail none Added crates/proto/src/frame.rs (MAX_FRAME, FrameError, write_frame, read_frame) re-exported from lib.rs; 13 fixture tests pass. Two compile fixes: mapped read_bytes io::Error to FrameError::Io and annotated serde_json::from_slice::; cargo-fmt reordered the lib.rs re-export lines; make gate prints gate: ok.
M1/05-proto-grant 2026-09-17 done 2 fail none Added crates/proto/src/grant.rs (Mode, Constraints with Default, Grant with serde defaults + deny_unknown_fields) re-exported from lib.rs and toml 1.1.6 as a proto dev-dependency (workspace dep + dependencies.md row); 4 fixture tests pass. cargo-fmt reordered the lib.rs re-exports before the gate.
M1/06-proto-records 2026-09-17 done 2 fail none Added crates/proto/src/audit.rs (DecisionRecord, AuditRecord) and crates/proto/src/log.rs (ToolCall, LogRecord) re-exported from lib.rs; 3 fixture tests pass, 40 total across the five proto test files. cargo-fmt reordered the lib.rs re-exports before the gate.
M1/07-brokerd-decision 2026-09-17 done 1 pass none Added Decision (Debug only, private fields), decide (Err(NoGrant) until M3) and the run stub (ToolResponse::Failed) in crates/brokerd; Decision::new carries expect(dead_code). 2 unit + 3 doctests (2 compile_fail) pass; verified the compile_fail guards by temporarily making new pub. make gate prints gate: ok.
M1/08-proto-strictness 2026-09-17 done 1 pass none Added deny_unknown_fields to AuditRecord and ToolCall in crates/proto; bounded Timestamp (MAX const, from_unix_millis -> Result, parse bounds via from_unix_millis, now clamps to MAX) in ids.rs. 45 proto tests pass; cargo fmt --all and make gate print gate: ok.
M1/09-gate-scripts-table-form 2026-09-17 done 1 pass none Rewrote check-lines, check-crate-deps and check-dep-docs to parse table-form ([dependencies.x]) and dotted (x.path) dependencies and to fail closed (exit 1 when crates/Cargo.toml/docs/dependencies.md is missing); check-lines now prints file has N lines (limit 500). Self-test passes with 0 failures, all three scripts pass on the real tree, and make gate prints gate: ok.
M2a/01-proto-sha256 2026-09-17 done 1 pass none Added crates/proto/src/hash.rs wrapping emsha 1.0.4 (HashError, Sha256 with new/update/finish, sha256, Default); re-exported from lib.rs, added emsha workspace dep and dependencies.md row. One compile fix: finish needed mut self to call finalize. 4 hash tests pass, make gate prints gate: ok.
M2a/02-inferproxy 2026-09-17 done 3 fail none Implemented Limits/TokenBucket/OpenGuard/serve/handle/forward in lib.rs (std only: the inference server's JSON is upstream's concern) and arg parsing + stale-socket handling in main.rs; TokenBucket counts thousandths of a token in u128 so partial refills stay additive and saturating_duration_since on an earlier now never underflows, capping at burst; serve refuses by rate-limit then open-connection limit; handle drops its OpenGuard before closing the client so a freshly-finished client is not refused on re-connect (rule 4). The scaffold proto dependency in crates/inferproxy/Cargo.toml was left untouched since the task's Modify/Copy lists cover only lib.rs, main.rs, the two test files and this log. First make gate failed on clippy::map_clone (main.rs used .map(String::clone)); switched to .cloned() and re-ran, then re-ran once more after restoring the proto scaffold — both pass. Hand test against straylight returned {"status":"ok"}; forward.rs passed 6/6 ten runs in a row.
M2a/03-loopd-config 2026-09-17 done 1 pass none Wrote crates/loopd/src/config.rs: Config + Infer/Slots/Expect/Sampling/Limits with deny_unknown_fields on all six and struct-level #[serde(deny_unknown_fields, default)] on Sampling and Limits; manual Default impls for the two; hand-written Display/std::error::Error ConfigError named by file. Everywhere check: all six structs (Infer, Slots, Expect, Sampling, Limits, Config) carry deny_unknown_fields. One local fix: Display used {path} on a PathBuf and failed to build, switched to path.display(). 6 config tests pass; make gate prints gate: ok.
M2a/04-loopd-http 2026-09-18 done 2 fail none Added pub mod http; to crates/loopd/src/lib.rs and wrote crates/loopd/src/http.rs (423 lines): Request/Head/HttpError, send (exact header order, no Content-Length for GET), read_head (buffers across timeouts; Timeout/Closed/TooLarge/Malformed), parse_status+parse_head (HTTP/1.1/HTTP/1.0, status 100-599, lowercased names, duplicates kept, trimmed values), body (chunked/length/close; chunk extensions + trailers skipped), and read_capped. Two fixes: the chunk Data phase jumped to Crlf on take==want, but want was capped by the caller's buffer so it switched mid-chunk and returned malformed chunk on the recorded fixture — changed to switch on chunk_remaining==0; read_length reads straight into the caller buffer instead of an 8192 temp slice that would panic for readers larger than 8192. First gate failed on three clippy lints (needless borrows of format! results and map_or -> is_some_and), fixed on the second run. All 15 tests pass and make gate prints gate: ok.
M2a/05-loopd-sse 2026-09-18 done 2 fail none Added pub mod sse; to crates/loopd/src/lib.rs and wrote crates/loopd/src/sse.rs: SseItem (Data, Done), SseError (Io, Timeout, Truncated, LineTooLong, NotUtf8) with Display/std::error::Error, and Events which reads a line in bounded 4096-byte chunks, skipping blank/comment/other-field lines and stripping data: plus one leading space, decoding UTF-8 only once a whole line has arrived. Two fixes: drain(..pos) left the newline in the buffer so blank lines never advanced — changed to drain(..=pos) and pop the endings; process_line returns Ok(None) for a skipped line, which collided with next_item's "stream ended" Ok(None) — restructured so a skip continues the loop and only a clean EOF sets ended. Both LineTooLong checks (mid-arrival and whole-read) verified by checking the accumulated length before reading and the finished line length. All 7 tests pass; first gate run failed on an unused import, fixed.
M2a/06-llama-request 2026-09-18 done 1 pass none Added pub mod llama; to crates/loopd/src/lib.rs and wrote crates/loopd/src/llama/mod.rs (ChatMessage, ToolSchema, ChatRequest, ChatEvent, FinishReason, Timings with the server's deserialize shape, Completion, InferError with Display/std::error::Error, and a Client holding the config) and crates/loopd/src/llama/request.rs. build_body serializes the body from #[derive(Serialize)] structs so key order is fixed; each message kind is serialized with its own struct (the assistant renders content as "" when None, and leaves reasoning_content/tool_calls out when absent; the top-level tools array is omitted when empty; type comes from #[serde(rename = "type")]); the first cargo build after writing the structs missed the role field on every message struct, caught by the failing test compile, added. 5 request tests pass; make gate prints gate: ok.
M2a/07-llama-assemble 2026-09-18 done 3 fail none Wrote crates/loopd/src/llama/assemble.rs (Assembler plus server-format Chunk/Choice/Delta/ToolCallPiece/FunctionPiece/PromptProgress structs with no deny_unknown_fields) and registered pub mod assemble;. Accumulation: text goes through get_or_insert_with so content/reasoning stay None until a non-empty piece arrives; tool-call pieces land by index via u32::try_from then usize::try_from and get_mut, a new call only at exactly the end, a skip-ahead or an out-of-range index is Protocol; timings update before the reasoning-token count reads predicted_n; finish checks finish_reason (StreamClosedEarly), then id, then every tool call has id and name. First gate failed on two clippy collapsible_if lints; rewrote the two nested ifs as edition-2024 let-chains and re-ran. All 7 assemble tests pass; make gate prints gate: ok.

Reviews

M1, tasks 01 to 07 — reviewed 2026-09-17 by the design model (Claude)

Verdict: accepted, with two follow-up tasks (08, 09). Nothing has to be redone.

Checklist from docs/plans/M1/README.md:

Check Result
Seven commits on m1, one per task, each with the Implemented-By trailer pass
All 23 copied files (tests, fixtures, Makefile, deny.toml, self-test) byte-identical to the plan pass
No change to the brief, specs, plans, AGENTS.md, CLAUDE.md; working tree clean; nothing pushed pass
make gate gate: ok, 45 tests
make audit advisories ok
No unwrap, expect, panic!, #[allow] or unsafe in library code; no dependency the tasks did not name pass
Field order, derives and signatures match the tasks pass

Process: first gate run passed in 4 of 7 tasks. The three failures were two compile fixes (task 04) and rustfmt reordering lib.rs re-exports (tasks 04 to 06). Commits run from 06:45 to 09:05.

Findings. "Implementer" means the task said it and the code missed it. "Task" means the task or its tests, written by the reviewer, were wrong or silent; the reference implementation had the same defect in both such cases.

# Severity Owner Finding Fix
1 medium implementer, and a gap in the given tests AuditRecord and ToolCall lack deny_unknown_fields. An audit line with an extra "forged":true field decodes. The given tests only checked the enums. Task 08; new tests/strict.rs checks every object at every depth
2 medium task Timestamp::from_unix_millis accepts any u64, but to_rfc3339 and serialization panic above year 9999, because humantime's Display returns an error and to_string() panics on that. Task 08
3 medium task [dependencies.brokerd] with workspace = true lets a role depend on another role while both dependency scripts pass. Dotted brokerd.path = … is also missed. The self-test had no such case. Task 09
4 low implementer All three scripts pass when ROOT/crates is missing, and hide tool errors with 2>/dev/null. A gate check that cannot look must fail. Task 09
5 low implementer check-lines.sh does not print the line count. Task 09
6 nit implementer frame.rs uses bounded as casts where try_from would say the same without a second look. read_bytes has two match arms that do the same thing. Constraints has a needless rename_all. ids.rs, class.rs, wire.rs have no module doc comment. Not worth a task; fix when next touched
7 low task The tasks told the implementer how to order lib.rs lines, and rustfmt disagreed, which cost three gate runs. AGENTS.md now says to run cargo fmt --all before the gate

Open question for the owner: the task 01 row says the skeleton files were "already present untracked from a prior attempt". The log has no row for that attempt, so its gate runs and the reason it ended are not recorded.

Answered by the owner, 2026-09-17: OpenCode was interrupted twice during task 01 because another process restarted llama-server. The owner started a new OpenCode session, which picked up the files the interrupted ones had left. The implementer did not abandon anything; the inference server went away under it.

On the experiment (review once per milestone): it held up for M1. None of the defects was built on by a later task, and all were found by reading the branch and probing it from outside. M1 is the easy case, though: types pinned by byte-exact fixtures. M2 has behaviour that fixtures cannot pin as tightly (a streaming HTTP client, the turn loop), so an early mistake there is more likely to be built on.

M1, tasks 08 and 09 — reviewed 2026-09-17 by the design model (Claude)

Verdict: accepted. M1 is complete. Both tasks passed the gate on the first run.

Check Result
Two commits with the trailer; only the listed paths staged; copied files identical to the plan; protected files untouched pass
make gate gate: ok, 50 tests
Reviewer's probes from the first review, run again unknown fields rejected in AuditRecord and ToolCall; out-of-range timestamps are Err, no panic
No 2>/dev/null left in the scripts; each fails when crates/ is missing pass

Task 08 was the smallest correct change: one attribute on each struct, Timestamp::MAX, a fallible from_unix_millis, parse routed through it, now() clamped.

Task 09 generalised beyond its self-test. The reviewer tried forms the self-test does not contain and the scripts handled them: a [target.'cfg(unix)'.dependencies] section, a table-form dependency under it, [build-dependencies], a table header with spaces, a multi-line inline table, and a commented-out dependency.

Remaining, recorded and not worth a task:

# Severity Finding
8 low check-lines.sh stops at the first file that is too long, so a second one is only reported after the first is fixed.
9 low A quoted key ("brokerd" = { path = "…" }) is not seen by either dependency script. The task did not list that form and the reference scripts miss it too. It does not happen by accident. The robust fix is to ask cargo metadata, which needs a JSON parser the gate does not have.