From 175bbfda9ca607c03cb0c85e3252534bb1066c8e Mon Sep 17 00:00:00 2001 From: Marcos Paulo Date: Tue, 13 Oct 2026 19:03:41 -0300 Subject: [PATCH] scripts: watchdog against runaway agents, resume instruction Co-Authored-By: Claude Sonnet 5.5 --- scripts/pi-wave.sh | 12 ++++++++++-- scripts/waves/_common.md | 2 ++ scripts/waves/wave2/whiteboard.md | 10 ++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 scripts/waves/wave2/whiteboard.md diff --git a/scripts/pi-wave.sh b/scripts/pi-wave.sh index 465837f..3e3d349 100755 --- a/scripts/pi-wave.sh +++ b/scripts/pi-wave.sh @@ -15,8 +15,16 @@ run() { cd "$wt" PATH="$HOME/.cargo/bin:$PATH" CARGO_TARGET_DIR="$root/target/agent-$task" \ timeout 3000 pi -p --mode json --no-session --no-extensions --no-skills --no-themes -np --thinking low \ - "$(cat "$root/scripts/waves/_common.md" "$1")" - ) >"$root/.worktrees/$task.jsonl" 2>&1 + "$(cat "$root/scripts/waves/_common.md" "$1")" >"$root/.worktrees/$task.jsonl" 2>&1 & + pid=$! + # watchdog: a runaway agent (rate-limit retry loop) floods its log; kill it past 40 MB + while kill -0 "$pid" 2>/dev/null; do + sz=$(stat -f %z "$root/.worktrees/$task.jsonl" 2>/dev/null || echo 0) + [ "$sz" -gt 41943040 ] && { pkill -P "$pid"; kill "$pid"; echo RUNAWAY >>"$root/.worktrees/$task.jsonl.flag"; } + sleep 10 + done + wait "$pid" + ) rc=$? # final assistant text -> .log; empty means the agent never answered python3 - "$root/.worktrees/$task.jsonl" >"$root/.worktrees/$task.log" <<'PY' diff --git a/scripts/waves/_common.md b/scripts/waves/_common.md index 6f8d0d7..e3f83b4 100644 --- a/scripts/waves/_common.md +++ b/scripts/waves/_common.md @@ -11,4 +11,6 @@ Rules, all mandatory: - Do NOT run git commit, git add, or touch git state. Do not run commands outside your worktree. - Final report (max 15 lines): what is implemented, the public API (signatures), test counts, anything skipped or any dependency you needed but did not have. +If your crate already contains files from an earlier attempt, read them first, keep what is correct and finish the task; do not start over. + YOUR TASK: diff --git a/scripts/waves/wave2/whiteboard.md b/scripts/waves/wave2/whiteboard.md new file mode 100644 index 0000000..966f12e --- /dev/null +++ b/scripts/waves/wave2/whiteboard.md @@ -0,0 +1,10 @@ +Crate: crates/whiteboard (notescraft-whiteboard, layer L1). SPEC section: 6 item 26 (Whiteboard bullet) and 12 (Whiteboard row). Allowed new dependencies: serde, serde_json, thiserror (`{ workspace = true }`). + +Implement an Excalidraw-v2-compatible model and a deterministic hand-drawn shape generator (no UI): +1. Model: `Whiteboard { elements: Vec, app_state: serde_json::Value, files: serde_json::Value, extra: Map }`. `Element` has common fields (id, type, x, y, width, height, angle, stroke_color, background_color, fill_style, stroke_width, stroke_style, roughness, opacity, seed, version, version_nonce, is_deleted, group_ids, bound_elements, link, locked, updated) and `kind: ElementKind::{Rectangle, Diamond, Ellipse, Arrow{points,start_binding,end_binding,start_arrowhead,end_arrowhead}, Line{points}, Freedraw{points,pressures}, Text{text,font_size,font_family,text_align,vertical_align,container_id}, Image{file_id}, Frame{name}, Unknown{type_name}}`; every unknown field at every level preserved in an `extra` map and re-emitted. JSON keys are camelCase as in Excalidraw files (`strokeColor`, `isDeleted`, ...). +2. `Whiteboard::from_str(&str)` accepting `{"type":"excalidraw","version":2,"source":..,"elements":[..],"appState":{..},"files":{..}}`; tolerant of missing optional fields; caps (32 MiB, 100000 elements); `to_string_pretty()` canonical output with `"type":"excalidraw","version":2`; `read(write(read(x))) == read(x)`. +3. Builders: `Element::rectangle/ellipse/diamond/line/arrow/text(...)` with sensible defaults and a deterministic seed from an `IdGen` (16-char ids from a counter, no rand crate); `Whiteboard::add`, `delete` (sets isDeleted, keeps element), `bounding_box`. +4. Hand-drawn style: `rough::outline(&Element, style: Style::{HandDrawn, Clean}) -> Vec` returning polylines (each `Vec<[f32;2]>`) for rectangle/diamond/ellipse/line/arrow. HandDrawn perturbs points using a seeded xorshift RNG from `element.seed` and `roughness` (double stroke, small endpoint overshoot); same seed and element give bit-identical output; Clean returns exact geometry. No NaN/inf for any input (zero size, huge sizes clamped). +5. `Whiteboard::from_mermaid_layout` is OUT of scope (another crate does it). + +Tests: fixtures in tests/fixtures (hand-written Excalidraw files: all element types, bound arrow, text in container, image with files entry, unknown element type, unknown fields everywhere); round trip and preservation tests; determinism test for rough outlines; degenerate geometry (zero width, NaN input from JSON is rejected); malformed JSON returns Err.