Files
multi-agent-mux/.agents/reports/reviewer-grok-01/report-d50d194d.md
T
GodopuandClaude Opus 5.5 3563a6fc17 release(v4.8.2): bump framework and eight skills to 4.8.2 in lockstep; archive the follow-up loop's reports
PR #41/#43 review follow-ups F1-F6, implemented by creator-claude-01 in a
loop and reviewed by claude, agy, grok and codex: round 1 (@95eabc6)
codex NOT PASS (a failed env load cached as loaded), fixed in 75f364e;
round 2 4/4 PASS. Full suite at 75f364e 1380 passed / 0 failed. Reviewer
reports (round 2 with identity sidecars) and the creator's reports go to
.agents/reports/<session>/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-10-03 00:27:24 +09:00

10 KiB
Raw Blame History

Review: follow-ups F1–F6 @ 95eabc6

  • Role: Reviewer (grok)
  • Identity: base 0051fd6c3b088f8b265928ccd94ea31053fdde7e, head 95eabc693f7346f491592b053487bdcf0acd5f4f, tree 46399844fa63d62d97f7b52ee35010f2fa1b7998, 8 commits, clean worktree
  • Verified at /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261001T230324Z-b511bf/1. Main stayed at 0051fd6. No product file was edited.
  • git diff --check 0051fd6..HEAD is clean. 27 files, +397/−88. No version bump, and nothing in the #17 item-4 / #39 / #40 / #42 scope.

F1. Day-of-month marker

mam_lstart is unchanged: ps -o lstart= piped through tr -s ' ' and a trim (loop_lock.sh:10-12). The only edit in that file is sourcing lib_int.sh for F4b.

test_a8_second_loop_is_refused_before_a_scheduler_exists now writes the marker with " ".join(out.split()) (tests/test_issue21_acceptance.py:268-272), the same collapse the product applies. It is parametrized real / padded. The padded case puts a ps shim first on PATH that answers lstart= with Thu Oct 1 08:02:03 2026 (two spaces) and asserts the marker stored Thu Oct 1 08:02:03 2026. The product, through the same shim, collapses to that string, so the holder stays live and the second loop is refused. That no longer depends on the calendar. Today is the 2nd, inside the old 1–9 failure window; this run passed both parameters.

The other live-marker writer, tests/test_o3_scoped_guard.py:57-59, already uses " ".join(out.stdout.split()). tests/test_o2_race_free_lock.py:40-44 still has a .strip() helper, and nothing calls it. Hard-coded Jan 1 markers in those files name a dead pid or a deliberately stale date, so the padding is not what the comparison decides.

F1's product behavior was already the collapse. The new test fails if a marker keeps the raw two-space lstart while mam_lstart collapses it. It passes on the previous loop_lock.sh for the same reason.

F2. One-line EnvFileError

load_env_or_report (mqtt_common.py:129-141) catches EnvFileError, prints ERROR: {path}: cannot read ({type}), and returns False. It does not call sys.exit. ensure_env_loaded still raises, so library callers keep the exception.

Each listed main returns 1 (job_subscriber returns RC_ERROR, which is 1 at job_subscriber.py:54):

Entry Call
publish_event.main publish_event.py:337-338
registry.main registry.py:536-537
job_subscriber.main job_subscriber.py:590-593
cli.main cli.py:60-61
bench.main bench.py:166-167
heartbeat.main heartbeat.py:496-497
remote.main remote.py:463-464

test_a1_python_entry_points_report_an_unreadable_file_in_one_line (tests/test_issue26_envfile.py:185-221) runs each main() on a mode-000 file and on broken UTF-8. It requires rc 1, stderr exactly that one line, the value one absent, and no registry directory created. On the previous mains the same call raised, so stderr was a traceback and this assertion failed.

F3. BOM, repeated keys, missing file

parse opens with encoding="utf-8-sig" (envfile.py:60-61), so one leading BOM is not part of the first key. Both loaders share that parser. test_a7_a_leading_bom_is_not_part_of_the_first_key (:414-425) requires FIRST and SECOND loaded and no skip warning. The previous utf-8 open failed that test.

Repeated keys stay first-wins in apply (unchanged). .mam.env.example states it (:24) and states the BOM (:23). test_a7_a_repeated_key_keeps_its_first_value_as_documented (:428-436) checks both loaders and that sentence. The sentence is what fails on the previous example.

MESSAGING.md:369 now says a missing explicit MAM_ENV_FILE is skipped, ambient search is refused, and a file that exists but cannot be read is EnvFileError with the one-line ERROR. That matches mqtt_common.py:51-54 (not a regular file: return) and envfile.py:63-64 (read or decode failure: raise). test_a7_a_missing_explicit_file_loads_nothing_and_says_nothing (:439-451) checks both loaders and the new sentence. The previous paragraph said an error is logged, so the doc assertion failed there.

F4. Integer knobs

mam_int / mam_int_knob moved unchanged into lib_int.sh. lib.sh sources it. loop_lock.sh and diff_collect.sh, which are sourced without lib.sh, source it themselves when the function is absent. reconcile.sh already sources lib.sh before POLL_INTERVAL (reconcile.sh:22, :41). deploy/remove.sh lists lib_int.sh only in the "is anything installed" fallback, next to lib.sh. Install copies the tree; check_assets_present is a sample, not the copy list.

(a) runner._int_knob (runner.py:629-644) uses _positive_int. Unset or empty keeps the default with no warning. An invalid value warns once and keeps the default, including MAM_CHILD_WAIT_S=abc, which used to raise ValueError.

Knob Minimum Default Call
MAM_WAIT_GRACE_S 0 (wait exactly the budget) 300 runner.py:627-628
MAM_CHILD_WAIT_S 0 (one look; max_wait 0 ends on that pass, runner.py:789-792) budget + grace runner.py:762-765
MAM_JOB_TREE_MAX 1 (0 would treat every tree as over) 50 runner.py:766-768

RUNNER_INT_KNOBS (tests/test_issue26_ints.py:71-75) is checked against the call sites (:286-291). 0.5 for the child wait is now invalid on purpose: item (a) puts that knob on the integer rule. The old float() path fails test_b1_runner_child_wait_is_decimal_or_the_default.

(b) These now go through mam_int_knob, minimums in INT_KNOBS (:46-57): MAM_READY_EMPTY_GIVEUP 5/min 1, SKS_EMPTY_GIVEUP 3/min 1, SKS_IDLE_RETRIES 2/min 0, SKS_IDLE_RETRY_BACKOFF_S 10/min 0, MAM_LOCK_STEAL_RETRIES 3/min 0, MAM_DIFF_MAX_BYTES 200000/min 1, MAM_DIFF_MAX_LINES 4000/min 1, RECONCILE_POLL_INTERVAL 15/min 1. test_issue6 sets the backoff to 0, which stays legal. SKS_EMPTY_GIVEUP=abc used to produce integer expression expected; test_b5_pane_quiescent_gives_up_on_empty_captures_with_a_bad_knob (:479-484) requires the warning and rc 2 without that error. loop_lock.sh and diff_collect.sh are sourced alone with abc, 1.5, and 0 (:461-476).

SKS_QUIESCENT_INTERVAL stays raw (lib.sh still passes ${SKS_QUIESCENT_INTERVAL:-0.5} into sleep). The default is the fraction 0.5, and mam_int would reject it. NOT_ARITHMETIC records that reason (test_issue26_ints.py:100-101).

(c) The loop skill states MAM_WAIT_GRACE_S (default 300, decimal integer ≥ 0, bad value warns and uses 300) on the job-budget row, and states MAM_CHILD_WAIT_S (0 means one look) and MAM_JOB_TREE_MAX (default 50, minimum 1) in the sub-job paragraph.

F5. No test switch in the watcher

MAM_TEST_UNRELIABLE_AGENT is gone from stall_watch.py. main still sets reliable from adapter.agent_status_reliable. S13 starts a one-shot process that sets that attribute to a property returning False on the adapter class, then calls sw.main (tests/test_issue30_stall_watch.py:196-201, :454-471). The assertions are the same: no idle notice while status is idle, then stalled:blocked with the detection text, and the log line idle judgement off for claude. test_s13_the_unreliable_case_needs_no_switch_in_the_product (:475-478) requires MAM_TEST_ absent from the product file, which fails on the previous source.

F6. The 60 s bound is the herdr wait

multi-agent-mux-delegate-job/SKILL.md now limits <= 60 s to one herdr wait, and says a publish already in flight runs to its end. The watcher still returns before a notice when the wrapper is gone after a wait (stall_watch.py:327-328), and _may_prompt checks again after the notice (:294-299, :406-409).

test_a_wrapper_gone_during_the_wait_gets_no_notice_and_no_nudge (tests/test_issue30_stall_watch_stop.py:418-436) no longer flips alive by call count. in_wait sets the wrapper gone on tick 2, inside wait_once, and the test requires the wait ticks to be [1, 2], every earlier alive check true, and the last check (2, False), with no notice and no nudge. That is the second wait, not the loop head before it. The after-wait check was already in the base; this tightens the test and the sentence around it.

Out of scope

The diff is the eight fix(followups) / test(followups) commits for F1–F6. lib_int.sh is the integer function those scripts share, not a new judgment. No stall close, budget extension, or session-id pin moved into bash.

Checks

From the review snapshot, MQTT variables unset, no .mam.env exported, interpreter and AGENT_PYTHON_BIN the control-plane venv, --basetemp outside the review tree:

tests/test_issue26_envfile.py, tests/test_issue26_ints.py, tests/test_issue21_acceptance.py, tests/test_issue21_loop_b.py, tests/test_issue30_stall_watch.py, tests/test_issue30_stall_watch_stop.py, tests/test_tier1_unit.py, tests/test_version_consistency.py

First run: 301 passed, 1 skipped, 2 failed in 450.42s. The skip is test_issue30_stall_watch.py's /proc marker (not linux). The two failures were test_a4_main_checkout_is_unchanged_by_the_whole_cycle and test_l11_diff_only_remote_reviewer_gets_shas_and_the_snapshot_diff: both reject the substring mam-review, and the basetemp path was /tmp/mam-review-d50d194d.*, so the sandbox's own path contained it. Rerun of those two with basetemp /tmp/d50follow.*: 2 passed in 2.28s.

Pre-fix failure of the new assertions is from the diff, not a second pytest on 0051fd6: F2's exact one-line stderr, F3's BOM load and the two doc sentences, F4's MAM_CHILD_WAIT_S=abc and SKS_EMPTY_GIVEUP=abc, and F5's MAM_TEST_ absence. F1 and F6 lock behavior the previous product already had.

Notes

  • reconcile.sh:173 still calls ensure_env_loaded() inside the embedded subscriber. An unreadable .mam.env there is still a traceback. F2's list is the seven mains, and those return the one line. The library function still does not exit.
  • tests/test_o2_race_free_lock.py:40-44 get_lstart still uses .strip() only. Nothing calls it.

[VERDICT: PASS]