Commit Graph
100 Commits
Author SHA1 Message Date
GodopuandClaude Opus 5.5 7c544c06ab test(issue-45): S11 base follows the review-R1 heredoc change; VERSIONS notes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:18:09 +00:00
GodopuandClaude Opus 5.5 811b080966 fix(issue-45): review R1-R3 - herdr checks the config, any TOML spelling kept, source never overwritten
R1: tomllib accepting a file is not herdr accepting it (a schema error makes
herdr drop the whole file, restore included). The shim passes REAL_HERDR and
the written file must pass `herdr config check` ("parse error"/"using defaults"
reject; unknown keys and sound-file notices do not), else the key-only file is
written and checked. A non-table `session` and a source that is not UTF-8 reach
that fallback instead of raising.

R2: the line edit is replaced by parse -> set -> serialize; the result must
parse back to the source plus the key, so dotted keys and inline tables keep
the user's other settings.

R3: the output is never the source (aliases included) nor a file not generated
for the same source; then a per-source name beside it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:17:59 +00:00
GodopuandClaude Opus 5.5 ce91cc11b9 release(v4.8.5): bump framework and eight skills to 4.8.5 in lockstep
Issue #45 A+B: MAM's isolated herdr servers start with restore-on-restart off,
and resume closes the session's own restored empty shell. Not reviewed yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:53:14 +00:00
GodopuandClaude Opus 5.5 eedcebcc12 fix(issue-45): restore stays off when python3 has no tomllib
The shim runs a bare python3. Before 3.11 the module failed to import and the
server started with restore on; now it gets the key alone, with a warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:34:27 +00:00
GodopuandClaude Opus 5.5 feceddf49f docs(issue-45): isolation and resume notes; block inventory line shifts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:32:49 +00:00
GodopuandClaude Opus 5.5 fb1380fdc1 fix(issue-45): resume closes the session's own restored empty shell
After a restart with restore off, the session's saved pane comes back as an
empty shell (in $HOME when its directory is gone) holding the agent_session it
reported. A resume with a CP_PREFIX cannot reuse it, so it stayed next to the
new pane. Just before spawning, resume closes panes whose agent_session is the
id being resumed, with no live agent and only the shell in the foreground; the
registry is not consulted, since a session whose server died stays running.

tests/test_issue45_restore.py drives a real herdr server under a throwaway HOME
and session with a fake claude: restore stays off through the MAM bootstrap
(with a restore-on control), the close conditions, and create -> crash ->
resume leaving one pane.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:32:49 +00:00
GodopuandClaude Opus 5.5 933af344c2 test(issue-45): S11 compares the shim heredoc with the Issue #45 bootstrap commit
S11 keeps the generated .mam/shim/herdr from changing by accident; #45 changes
it on purpose, so its base moves to ed73f67.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:32:49 +00:00
GodopuandClaude Opus 5.5 ed73f67a4f fix(issue-45): the shim bootstrap starts MAM servers with restore-on-restart off
The headless bootstrap regenerates the server config on every start and hands
it to the server as HERDR_CONFIG_PATH. Without a file the server still starts,
with a warning. mam_sandbox unsets HERDR_CONFIG_PATH/XDG_CONFIG_HOME so the
file lands in the sandbox HOME.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:32:35 +00:00
GodopuandClaude Opus 5.5 a640b0f072 feat(issue-45): herdr config for MAM servers with restore-on-restart off
lib_py/herdr_config.py builds the user's herdr config (HERDR_CONFIG_PATH, else
XDG_CONFIG_HOME, else ~/.config, as herdr reads it) with only
[session] resume_agents_on_restore forced to false, written next to the source
so relative paths resolve the same. The text merge is re-parsed; a merge that
does not leave the rest equal falls back to the key alone, since herdr drops a
config that does not parse and would restore again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:32:35 +00:00
Godopu c5c7eff6f7 merge: fix/issue-42-skills-link (.claude/skills check takes both local layouts; v4.8.4)
Reviewed in two rounds by claude, agy, grok and codex (4/4 PASS on 150b493).
2026-10-04 04:54:58 +00:00
GodopuandClaude Opus 5.5 65dc7fde82 release(v4.8.4): bump framework and eight skills to 4.8.4 in lockstep; archive PR #47 reviews (two rounds)
The .claude/skills check accepts both local layouts and fails on a dangling
link. Review: round 1 (@e1ed270) claude agy grok PASS, codex NOT PASS (a
dangling top-level link was skipped); fixed in 150b493; round 2 4/4 PASS.
Reports and the round-2 identity sidecars 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-04 13:54:57 +09:00
GodopuandClaude Opus 5.5 150b4937f8 test(issue-42): review R1 - a dangling .claude/skills link fails instead of skipping
Path.exists() follows the link, so a dangling top-level .claude/skills read
as "absent" and the test skipped: every skill's load path could be broken
with the suite still green (codex R1 on PR #47; the exists() branch predates
this PR).

The check moves into claude_skills_problems(link, skills_dir, names):
absence is "neither exists nor is a symlink"; a symlink must resolve to
.agents/skills; a directory must hold each skill resolving to the matching
.agents/skills/<skill>. test_claude_skills_layouts runs it on synthetic
layouts (absent, single link, per-skill links, a link to the wrong skill, a
dangling top-level link, a dangling per-skill link), so it is exercised in
a worktree too. Replaying the review's probe (a dangling .claude/skills in
the tree) now fails; the main checkout's per-skill layout reports nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-10-04 13:46:41 +09:00
GodopuandClaude Opus 5.5 e1ed2708d8 test(issue-42): the .claude/skills check accepts both local layouts
test_claude_skills_link_resolves_to_agents_skills assumed .claude/skills is
one symlink to .agents/skills. It is local and git-ignored, and a checkout
may instead hold a directory with one link per skill (this workspace's
layout), where every link is correct but the test failed. PR #46's reviews
and suites ran in worktrees, where the directory is absent and the test
skips, so the failure only showed in the main checkout after the merge.

The test now accepts a single link to .agents/skills, or a directory whose
entry for each of the eight skills resolves to the matching
.agents/skills/<skill>. Checked against no link (skip), per-skill links
(pass; the old test fails), one link pointing at the wrong skill (fail) and
a single link (pass).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-10-04 08:38:01 +09:00
Godopu 6af4a796b1 merge: fix/issue-42 (Issue #42 stages 1-2: SKILL.md code-block inventory, duplicate blocks removed; v4.8.3)
Refs #42 (stages 1-2). Reviewed in two rounds by claude, agy, grok and codex (4/4 PASS on 17eba09); full suite 1408 passed / 0 failed.
2026-10-03 23:36:27 +00:00
GodopuandClaude Opus 5.5 44999ff2fb release(v4.8.3): bump framework and eight skills to 4.8.3 in lockstep; archive PR #46 reviews (two rounds)
Issue #42 stages 1-2: the SKILL.md code-block inventory as test data and
the removal of blocks the entry scripts already do. Merge-workspace review:
round 1 (@8d26106) claude agy grok PASS, codex NOT PASS (R1-R3); fixed in
17eba09 (docs, inventory data and the guard test only); round 2 4/4 PASS.
Full suite at 8d26106 1408 passed / 0 failed. Reports and the round-2
identity sidecars 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-04 08:36:15 +09:00
GodopuandClaude Opus 5.5 17eba0904e docs(issue-42): review - attach names the herdr server, --dry-run is not "write nothing", a deleted block stays deleted
Merge-workspace review of PR #46 (codex R1-R3 and C1, claude O1 and O4).

- R1: resume never attaches, and the server it exports does not reach the
  calling shell; the native herdr binary does not route on environment
  variables. The attach advice and the Verification block now name the
  server: `herdr --session <server> agent attach <name>`, with <server> the
  row's herdr_session.
- R2: --dry-run in resume and create skips the spawn and the registry
  update, but the checks before it still run and on macOS remove the
  quarantine attribute from the agent binary. The option rows say so.
- R3: test_no_deleted_body_came_back compares every current block with the
  deleted bodies by exact sha and by a whitespace-insensitive fingerprint
  (stored as base_fingerprint), independently of the inventory, so a
  deleted block cannot return by being listed again under class c.
  Replaying the review's probe (the deleted resume Workflow re-added and
  inventoried as c) fails the new test and passes the old one.
- C1 / O1: the base create call block at 263-266 (class c) folded into the
  Workflow call is recorded under "superseded"; the 37 base blocks are all
  accounted for.
- O4: create exit 42 also covers MAM_KILL_ON_READY_TIMEOUT=1; stop's no-op
  exit 0 does not apply with --purge-conversation.
- The inventory's positions and shas are regenerated for the edited blocks;
  every classification is unchanged.

No product code changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-10-04 08:06:32 +09:00
GodopuandClaude Opus 5.5 8d26106195 test(issue-42): review R2 - a passthrough code must be in the exit table
raised == passthrough forced the declaration, but nothing checked the
declared code against the table: dropping create's exit 4 from both the
table and the data still passed. Assert passthrough <= table codes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 14:10:59 +00:00
GodopuandClaude Opus 5.5 e6a46cd7c4 fix(issue-42): review R1 - stop's exit 1 also comes after the stop
The registry update after the kill chain raises SystemExit(1) when the row
disappeared during the run (stop_session.sh:310), so "exit 1 changed nothing"
was false on that path. The stop table now says so, the code is declared as a
passthrough, and the test requires every integer SystemExit in an entry
script's heredocs to be declared - the check that would have caught it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 13:56:36 +00:00
GodopuandClaude Opus 5.5 7670b41afd docs(issue-42): stages 1-2 - SKILL.md code-block inventory as test data; remove blocks the scripts already do
Stage 1: tests/data/skill_code_blocks.json lists all 37 fenced blocks of the
eight SKILL.md files at base 45d1621 (indented fences count), classed (a)
duplicate of a script, (b) procedure no script offers, (c) example/call/
diagram, with script file:line evidence and recorded mismatches.
tests/test_issue42_skill_blocks.py fails when a block changes without the
inventory, an (a) block comes back, a removed block's entry call is missing,
an exit-code table drifts from the script, or evidence points nowhere.

Stage 2: the five (a) blocks are gone - resume Workflow, stop pre-flight,
create pre-flight / Workflow / manual YAML append - replaced by the
entry-point call with option and exit-code tables. Each exit table has a
catch-all row: under set -e a failing sub-step's code passes through.
create's exit 4 is real (the YAML write raises SystemExit(4) for a duplicate
running row) and is listed as such. Doc-to-script mismatches are fixed in the
doc (resume Case 3 / no attach, stop --purge-conversation --yes, status
--json keys, create cmd_full and the nonexistent yaml-edit script). No
behaviour change, no version bump.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 13:42:08 +00:00
Godopu 45d1621c93 merge: fix/followups-41-43 (PR #41/#43 review follow-ups; v4.8.2)
Reviewed in two rounds by claude, agy, grok and codex (4/4 PASS on 75f364e); full suite 1380 passed / 0 failed.
2026-10-02 15:27:28 +00:00
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
GodopuandClaude Opus 5.5 75f364e4b4 fix(followups): F2 review R1 - a failed env load is not cached as loaded
ensure_env_loaded() set _env_loaded before _load_dotenv() ran. With F2,
load_env_or_report() turns the EnvFileError into a return value, so a second
main() in the same process skipped the unreadable file and ran unconfigured
(codex R1: registry.main twice -> rc 1 then rc 0 with a job registered; a
repaired file was never read). The flag is now set only after a successful
load: a failure is retried on the next call, a success still loads once.
envfile parses the whole file before applying, so a failed load sets nothing.

MESSAGING.md: the one-line ERROR is the Python entry points'; the wrapper
adds its own "ERROR: cannot load <path>" line (codex, minor).

Test: in one process, registry.main twice on an undecodable file is rc 1
both times, prints the ERROR line twice and registers nothing; the repaired
file is applied on the next call; a later change is not re-read. At 95eabc6
it fails with rc [1, 0], one job registered and the value unset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
2026-10-02 09:29:11 +09:00
GodopuandClaude Opus 5.5 95eabc693f fix(followups): F2 - job_subscriber returns the env-file failure by name
5edf913 gave job_subscriber.main a bare `return 1`; its exit codes are
returned by name (test_issue5 test_job_subscriber_cli_contract_snapshot).
The value stays 1 - what the EnvFileError traceback exited with before -
and is RC_ERROR. The wrapper reads the same file first and exits 1 itself,
so it never starts a subscriber that would report this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
2026-10-02 08:39:22 +09:00
GodopuandClaude Opus 5.5 1e92ddc563 test(followups): F6 - the wrapper vanishes inside the second wait; the <= 60 s is the herdr wait's
delegate-job SKILL.md said a watcher outlives a killed wrapper by "one
wait, <= 60 s". That bound is the herdr wait's; a wrapper that goes while a
notice is being published is noticed when that publish ends. The text now
says both (PR #41 codex round 3).

"a wrapper gone during the wait" switched alive() by call count, so the
watcher saw the wrapper gone at the next loop head and never entered the
second wait (ticks == [1] with that band). The wrapper now vanishes inside
the second herdr wait (_during_notice's in_wait hook), and the test checks
that the second wait happened and that alive() was true until then. The
same test fails when the post-wait _wrapper_gone() check is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
2026-10-02 08:23:31 +09:00
GodopuandClaude Opus 5.5 c252ac4d54 fix(followups): F5 - no test switch in the stall watcher
stall_watch read MAM_TEST_UNRELIABLE_AGENT to pretend an adapter's
agent_status was unreliable (PR #41 M7). The branch is gone. S13 now runs
the real stall_watch.main() in a process where the adapter itself reports
agent_status_reliable = False (_UNRELIABLE_MAIN), and checks the same
things as before: no idle notice or nudge, the blocked notice, and the
"idle judgement off" log line. A static check keeps MAM_TEST_ out of the
watcher (fails at 0051fd6). Without the seam S13 fails on this tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
2026-10-02 08:23:31 +09:00
GodopuandClaude Opus 5.5 fb1b93c665 fix(followups): F4b - bash integer knobs that reach [ -ge/-le/-gt ] or sleep go through mam_int_knob
A non-numeric MAM_READY_EMPTY_GIVEUP, SKS_EMPTY_GIVEUP, SKS_IDLE_RETRIES,
SKS_IDLE_RETRY_BACKOFF_S, MAM_LOCK_STEAL_RETRIES, MAM_DIFF_MAX_BYTES,
MAM_DIFF_MAX_LINES or RECONCILE_POLL_INTERVAL was a `[` error or a sleep
error at the point of use (PR #43 review claude R3). They now go through
mam_int_knob: decimal, a warning and the default for a bad value. Minimums
are in test_issue26_ints.py INT_KNOBS: 1 for the give-up counts (0 acts as
1), the diff caps (0 truncates every diff) and the poll interval (0 is a
busy loop); 0 for the retry counts (one attempt) and the retry backoff.

SKS_QUIESCENT_INTERVAL stays raw: its default is 0.5 s and sleep's
fraction is its meaning; NOT_ARITHMETIC says so.

loop_lock.sh and diff_collect.sh also run without lib.sh (the Python runner
and the tests source them alone), so mam_int / mam_int_knob move verbatim
to lib_int.sh, which lib.sh sources and those two source when the function
is missing. remove.sh's no-manifest fallback list names the new file
(install copies every file under .agents/skills). test_issue21_loop_b l15,
which builds a control root holding only diff_collect.sh, copies it too.

Tests: the table guard now reads every shell file; scripts sourced alone
read their knobs as decimal; _pane_quiescent with SKS_EMPTY_GIVEUP=abc
gives up with a warning and no `[` error (14 of the new cases fail at
0051fd6).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
2026-10-02 08:23:31 +09:00
GodopuandClaude Opus 5.5 8b046390be fix(followups): F4a/F4c - the runner's wait knobs follow the integer rule; MAM_WAIT_GRACE_S documented
MAM_WAIT_GRACE_S silently fell back to 300 on anything but bare digits
(" 30 " too), MAM_CHILD_WAIT_S went through float() (abc raised ValueError,
0.5 and 1e3 were taken) and MAM_JOB_TREE_MAX through int() (abc and an
empty value raised). All three now go through runner._int_knob, which is
_positive_int's rule (PR #43 review grok): decimal, blanks around allowed,
an invalid value warns once and the default is used. Minimums:
MAM_WAIT_GRACE_S >= 0 (wait exactly the budget), MAM_CHILD_WAIT_S >= 0
(look once; test_issue34 uses 0), MAM_JOB_TREE_MAX >= 1 (a cap of 0 calls
every tree "over"; not waiting is MAM_CHILD_WAIT_S=0).

The loop SKILL.md now names MAM_WAIT_GRACE_S in the budget row and the
sub-job paragraph (PR #41 M6), with the knobs' integer rules.

Tests: RUNNER_INT_KNOBS in test_issue26_ints.py holds the minimums; the
real wait_for_descendants is driven per knob, and a static check ties the
call-site minimums to the table (15 of the new cases fail at 0051fd6; the
14 that pass are valid values whose result did not change).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
2026-10-02 08:13:44 +09:00
GodopuandClaude Opus 5.5 71ea8095c5 fix(followups): F3 - .mam.env BOM, repeated keys and a missing MAM_ENV_FILE
PR #43 review (grok):
(a) A UTF-8 byte-order mark at the start of the file made the first line an
    "invalid key", so its setting was dropped. The one parser (envfile.py,
    used by the wrapper and mqtt_common) now reads with utf-8-sig.
(b) A key given twice keeps its first value (a later line does not replace
    it). The behaviour is unchanged; .mam.env.example's FORMAT section and
    envfile.py's docstring now say so.
(c) MESSAGING.md said a MAM_ENV_FILE naming no file logs an error and makes
    connections fail closed. Both loaders skip it silently and refuse the
    ambient search; a file that exists but cannot be read is the error
    (EnvFileError, one ERROR line and rc 1). The text now says that.

Tests A7: BOM in both loaders, first-value-wins in both loaders plus the
FORMAT text, and a missing explicit file loading neither it nor the ambient
.mam.env without an error plus the MESSAGING text (all three fail at 0051fd6).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
2026-10-02 08:10:42 +09:00
GodopuandClaude Opus 5.5 5edf913d22 fix(followups): F2 - Python entry points report an unreadable env file in one line
A .mam.env that exists but cannot be read or decoded stopped the Python
entry points with an EnvFileError traceback, while the wrapper printed one
`ERROR:` line and exited 1 (PR #43 review claude S1). The mains that load the
env file - publish_event, registry, job_subscriber, cli, bench, heartbeat and
remote - now call mqtt_common.load_env_or_report(), which prints
`ERROR: <path>: cannot read (<exception type>)` and lets the main return 1.
No value or file content is printed. ensure_env_loaded() still raises: no
library function exits the process.

Test: every entry point, for a 000 file and an undecodable one, ends with
exactly that line and rc 1 and creates nothing (14 cases; all 14 fail at
0051fd6 with a traceback).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
2026-10-02 08:09:46 +09:00
GodopuandClaude Opus 5.5 75936f11c6 test(followups): F1 - A8 writes the marker lstart as loop_lock.sh reads it
`ps -o lstart=` pads a one-digit day ("Oct  1"); loop_lock.sh's mam_lstart
squeezes the spaces before comparing, but A8 wrote the raw value, so the
live holder's marker looked stale and the second loop was not refused on
the 1st-9th of every month. The test now normalises like the product (the
product is unchanged) and also runs with a `ps` shim that always prints a
padded lstart, so the padded case is checked on every date.

The other tests that record a live lstart (test_o3_scoped_guard
_get_lstart) already normalise; test_o2's markers carry fixed stale values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
2026-10-02 08:08:26 +09:00
Godopu 0051fd6c3b merge: fix/issue-26 (Issue #26 one .mam.env parser, decimal integer knobs, tests on a local MQTT broker; v4.8.1)
Closes #26. Reviewed in two rounds by claude, agy, grok and codex (4/4 PASS on 0eec795).
2026-10-01 15:23:26 +00:00
GodopuandClaude Opus 5.5 2e90c809d3 release(v4.8.1): bump framework and eight skills to 4.8.1 in lockstep; archive PR #43 reviews (two rounds)
Issue #26: one .mam.env parser for the wrapper and the Python tools,
decimal integer knobs, tests on a local MQTT broker. Merge-workspace
review: round 1 (@b58edac) claude agy PASS, codex NOT PASS (R1-R4), grok
no verdict (its late report, archived as report-c9ed5b14.md, found R1 too);
round 2 (@0eec795) 4/4 PASS. Reports and round-2 identity sidecars 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-02 00:23:22 +09:00
GodopuandClaude Opus 5.5 0eec795cd1 fix(issue-26): review - an unreadable env file is an error, NUL lines are skipped, integers are ASCII in every locale, empty flags are refused
Merge-workspace review of PR #43 (codex R1-R4, claude R1/R2).

- R1: envfile.parse raises EnvFileError when the file exists but cannot be
  read or decoded as a whole; `--shell` prints the cause and exits 1, so the
  wrapper stops (rc 1, as before #26) instead of running on defaults, and
  mqtt_common's entry points stop on the same error. A bad single line is
  still a warning. The wrapper's message no longer claims a read failure
  when the cause is elsewhere ("cannot load ... see the line above").
- R2: lib.sh mam_int spells out the ASCII blanks and digits instead of
  [[:space:]]/[0-9], which follow the locale: U+2003 30 U+2003 was 30 in the
  wrapper and invalid in the runner under en_US.UTF-8. The test oracle
  _expected uses the same ASCII rule.
- R3: an integer flag given explicitly with an empty value is refused like
  any other invalid value; only an absent flag keeps its default.
- R4: a line holding a NUL byte is skipped with a warning by both loaders;
  before, the wrapper read "leftright" and the Python loader died.
- .mam.env.example states the format: no expansion, `export` and bash's own
  variables skipped, a trailing `# ...` is part of the value, the environment
  wins, an unreadable file is an error.

Tests: unreadable (0000) and invalid-UTF-8 files stop the wrapper and the
Python loader; a NUL line is skipped by both; Unicode blanks/digits are
invalid for mam_int, _positive_int, the wrapper and the runner under C and
en_US.UTF-8; explicit empty values for the five integer flags are refused.
All of them fail on b58edac.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-10-01 22:59:57 +09:00
GodopuandClaude Opus 5.5 b58edace11 test(issue-26): C0 reads broker state only after the matching ack; the local broker acks a PUBLISH after routing it
Review (fa45b0e6): paho's connect() does not wait for the CONNACK, so C0
could read protocol_levels before the broker had handled the second
CONNECT, and the `before` slice could take in a previous test's connection.

- _Client waits for CONNACK (and UNSUBACK, instead of sleeping 0.3 s);
  C0 checks the levels of its own two client ids (stats() levels_by_client).
- The local broker now routes and stores a PUBLISH before its PUBACK, so a
  publisher holding the ack may rely on the retained state it asked for
  (late subscriber replay, retained delete).
- test_c0_holds_when_the_broker_is_slow runs C0 with CONNECT/UNSUBSCRIBE
  delayed and PUBLISH routed late; the old C0 fails it ([] == [4, 4]), as do
  an ack-before-route broker and a fixed-sleep unsubscribe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:04:19 +00:00
GodopuandClaude Opus 5.5 fb006374d6 test(issue-26): test_bug4 checks the whole send_keys_safe body, not its first 2500 characters
34241d7 added a line near the top of send_keys_safe (SKS_QUIESCENT_TRIES
normalisation), which pushed `agent prompt` from inside the 2500-character
window to offset 2519. The order the test guards is unchanged
(_pane_quiescent < _pane_dialog_open < agent prompt); it now slices up to
the function's closing brace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 20:13:40 +00:00
GodopuandClaude Opus 5.5 34241d7a65 fix(issue-26): review round 1 - no file key assigns bash's own variables; SKS_QUIESCENT_TRIES; long decimals
R1 (P1): a quoted export still ran code when the key was a bash special
variable (RANDOM=a[$(cmd)] is evaluated arithmetically on assignment).
envfile.RESERVED (bash's own variables, plus BASH_/COMP_/READLINE_
prefixes) is skipped with a line-numbered warning by both loaders; a test
keeps it covering every variable a clean bash defines. The wrapper also
hands SCRIPT_DIR/REPO_ROOT/TARGET_ENV to the parser as environment, so the
file cannot replace the variables it has already set.

R2 (P2): SKS_QUIESCENT_TRIES reached `for ((i < tries))` through a function
argument; it is normalised at the call site (minimum 0). B5 now requires
every integer-looking env default in the shell code to be normalised or
listed in NOT_ARITHMETIC with where it goes, and exercises _pane_quiescent.

R3 (P2): _positive_int strips leading zeros and rejects more than nine
significant digits before int(), like mam_int; 5000-digit inputs no longer
raise in the runner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:39:08 +00:00
GodopuandClaude Opus 5.5 2826e709ae test(issue-26): no test depends on a third-party MQTT broker
- tests/_local_mqtt.py: in-process MQTT 3.1.1 broker on 127.0.0.1 (what
  the repo uses: protocol level 4, clean session, QoS 0/1, retained).
- conftest: one session broker named by MQTT_BROKER/MQTT_PORT/MQTT_TLS;
  other redirecting/auth MQTT_* dropped and restored afterwards.
- Harnesses of the 22 broker-dependent tests (#18 acceptance1/2/3/5,
  #6 t1_1/2/7/8/14/15/16/20, #19 MQTT loop x2, #33 b3/r3/b7, #30
  s9/s10/r2) use it; the "broker unreachable" skips are gone (a local
  broker that does not deliver is a failure). acceptance1/5 and #19 also
  assert on the subscriber, runner and audit logs the real producers write.
- tests/test_local_mqtt.py: conformance with the real clients and the real
  publish_event -> job_subscriber path (C0), an AST guard against public
  broker literals and MQTT_* deleted without a replacement (C2), and the
  session's broker config (C3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:54:58 +00:00
GodopuandClaude Opus 5.5 80dc2eb45e fix(issue-26): integer knobs that reach bash arithmetic are read as decimal
bash read MAM_JOB_TIMEOUT_S=0700 as octal 448 (and failed on 0900) while
the loop runner read 700. One rule now, with a per-knob minimum:
lib.sh mam_int / mam_int_knob and runner._positive_int (blanks allowed,
digits only, decimal, min <= n <= 999999999).

- minimum 1: MAM_JOB_TIMEOUT_S, MAM_HEARTBEAT_SEC, MAM_HEARTBEAT_MISSES,
  MAM_ACK_TIMEOUT_S (invalid env: warning + default) and the wrapper's
  --timeout/--idle-timeout/--heartbeat/--heartbeat-misses/--ack-timeout
  (invalid: rc 1); the runner's job_budget, --job-timeout, contract_args.
- minimum 0 (0 = check once): MAM_INJECT_ACK_GRACE_S,
  delegate_wait_started's ack_s, SKS_DIALOG_TIMEOUT, SKS_BUSY_TIMEOUT,
  SKS_DELIVERY_GRACE_S.
- tests/test_issue26_ints.py: INT_KNOBS is the one table of minimums; a
  static guard keeps env defaults out of arithmetic and checks each
  normalised knob against it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:47:40 +00:00
GodopuandClaude Opus 5.5 f1331d36ce fix(issue-26): the wrapper reads .mam.env through the Python loader's parser, never source
- lib_py/delegation/envfile.py (stdlib only) holds the one set of rules:
  comments and blank lines skipped, key and value stripped, one matching
  quote pair removed, no shell interpretation, keys must match
  ^[A-Za-z_][A-Za-z0-9_]*$ (bad or `=`-less lines warn with the line
  number, never the value).
- mqtt_common._parse_env_file delegates to envfile.apply.
- The delegate-job wrapper runs envfile.py --shell by path and evals the
  shlex-quoted exports; values never reach a command line. The environment
  now wins over the file in both loaders.
- .mam.env.example: commented assignments no longer use $HOME, which the
  parser keeps literally.

The shell parsed the env file in one place only (wrapper :38), not three.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:47:40 +00:00
Godopu e1eb22bd59 merge: fix/issue-30-33 (Issue #33 budget + wall_clock_timeout, Issue #30 notify-only stall watcher; v4.8.0)
Closes #30 #33. Reviewed in three rounds by claude, agy, grok and codex (4/4 PASS on 9200562); release 53f87e4.
2026-09-26 07:08:46 +00:00
GodopuandClaude Opus 5.5 53f87e4560 release(v4.8.0): bump framework and eight skills to 4.8.0 in lockstep; archive PR #41 reviews (three rounds)
Issue #33 (job budget, wall_clock_timeout vs deadline_timeout) and
Issue #30 (notify-only stall watcher). Reviews of PR #41 in the merge
workspace: round 1 (@1673818) claude N1 / codex R1 R2 NOT PASS, agy grok
PASS; round 2 (@af0a2a2) grok N1 / codex D1 D2 NOT PASS, claude agy PASS;
round 3 (@9200562) 4/4 PASS. Reports and the round-3 identity sidecars go
to .agents/reports/<session>/. The design consensus on items A-D is in the
VERSIONS.md entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-26 16:08:37 +09:00
GodopuandClaude Opus 5.5 9200562c91 fix(issue-30): review round 2 - the watcher is stopped with KILL, and a strict last gate sits between the notice and the keys
grok N1: the wrapper spent up to 5 s on a TERM budget before it finalized the
job, so when the watcher swallowed the TERM the silent job's close came ~5 s
late and a `completed` written in that gap won (test_issue14 idle-grace).
codex D1: `_notify` blocks on a publish for up to 120 s, and the prompt went
out right after it; a completed, a re-dispatch or an unreadable record in that
window still got the nudge. codex D2: the wrapper pid was checked only at the
loop head, so a watcher whose wrapper vanished during a wait could still
notify and nudge once.

- multi-agent-mux-delegate-job `stop_stall_watch`: KILL + wait, no budget.
  The watcher holds nothing to flush (its TERM handler is a bare os._exit),
  and KILL cannot be swallowed.
- lib_py/stall_watch.py: `_wrapper_gone()` after every wait (before any
  notice) and at the loop head; `_may_prompt()` right before the keys - the
  wrapper is alive and the record, read then, is open and this dispatch;
  `_dispatch_open(strict=True)` treats an unreadable record as "no".
- SKILL.md: the lifetime contract states the one-wait lag and that nothing is
  sent after the wrapper is seen gone.
- tests/test_issue30_stall_watch_stop.py: the stop returns at once against a
  TERM-ignoring stand-in (took 6 s on the round-2 code); completed,
  re-dispatch and a torn record during the notice publish withhold the nudge;
  a wrapper gone during the wait gets no notice and no nudge. All five fail
  on the round-2 code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-26 15:28:07 +09:00
GodopuandClaude Fable 5.1 af0a2a2557 fix(issue-30): review - the stall watcher's lifetime is bounded on both sides and it re-checks its dispatch before acting
Round-1 review of PR #41 (claude N1, codex R1/R2).

N1 - the wrapper stopped its stall watcher with `kill` followed by an
unbounded `wait`. A TERM that lands in the watcher's fork-exec window is
swallowed (bash defers terminating signals in the forked child until the
exec), the watcher then installs its own handler and lives until the job
closes, and the wrapper hung until someone killed the watcher by hand
(reproduced twice in test_issue14's ack test; the wrapper trace stops at
`kill` + `wait`).
R1 - a wrapper that is itself terminated left the watcher orphaned.
R2 - a `completed` or a re-dispatch that landed during a blocking herdr
wait (up to 60 s) was still followed by the idle notice and the nudge.

- multi-agent-mux-delegate-job: `stop_stall_watch` sends TERM, polls up
  to 5 s, then KILLs and reports "ignored TERM; killed"; `start_stall_watch`
  passes `--watch-pid $$`.
- lib_py/stall_watch.py: `--watch-pid` (rc 0 once that pid is gone, checked
  between herdr calls); `_dispatch_open()` re-reads the record after every
  wait and right before the nudge - terminal status, another dispatch token
  or attempt, or a missing record ends the run without a notice; a refused
  `stalled:idle` publish withholds the nudge (`_notify` returns False).
- delegate-job SKILL.md: the watcher's lifetime contract; exit-code rows 2
  and 3 name the idle clock and the wall-clock label; "before v4.7.5" ->
  v4.8.0 (M5, NB2).
- tests/test_issue30_stall_watch.py (M8): the module-wide Linux mark is
  narrowed to S6 (/proc); r4's stub stamps time.time() so its cross-process
  comparison holds on macOS too (47 passed, 1 skipped here).
- tests/test_issue30_stall_watch_stop.py (new, macOS and Linux): the stop
  routine against a TERM-ignoring stand-in returns in 4-12 s; the wrapper
  returns (rc 3) with a TERM-ignoring watcher shim; `--watch-pid` ends the
  watcher; a TERM to the real wrapper leaves no watcher; a job completed or
  re-dispatched during the wait is not nudged; a refused notice withholds
  the nudge. Every one of them hangs or fails on the round-1 code.

Not in this commit (follow-ups): M7 test hook in product code (no adapter
is unreliable, a seam is needed), M9/B3 public-broker latency (on this host
even `started` sometimes never reaches the subscriber), the leading-zero
budget parse (NB1) and M1/M2/M4/M6 go on the #17 item 4 list.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-26 13:17:24 +09:00
GodopuandClaude Opus 5.5 1673818bb5 fix(issue-33): subscriber exit codes are named on both sides; wait keeps a spent budget at rc 2; G-8 is deterministic
- job_subscriber.py returns only named codes (RC_COMPLETED 0, RC_ERROR 1,
  RC_NO_EVENT 2, RC_BROKER_UNAVAILABLE 3, RC_WALL_CLOCK 4). The wrapper
  mirrors them as SUB_RC_* and compares subscriber codes only by name; a test
  keeps the mirror equal to the Python constants and forbids numeric
  comparisons of $sub_rc/$sub_exit and numeric `return` in the subscriber.
- `delegate-job wait` passed the subscriber's code straight through, so a
  spent budget would have reached its callers as 4; it is 2 again (B7 fails
  without the mapping).
- tier1 G-8 gave both clocks 0.5 s and raced; the wall clock now runs out
  first by construction (idle 30 s) and the test asserts RC_WALL_CLOCK.
  Other subscriber rc asserts use the names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 00:22:43 +00:00
GodopuandClaude Opus 5.5 9ec0569401 fix(issue-30): review ad3d8206 - a prompt glyph inside a draft is draft text; the R4 regression test tells the defect apart
- input_region: in the prompt-line shape the input box starts only at a
  column-0 prompt (codex indents a draft's continuation lines), and the
  prompt and the (whole-line) placeholder are stripped from the box's first
  non-blank line only, so `› Explain this terminal prompt:\n  ›` is not empty.
- test_r4 runs with S=40 again: the defective code nudged ~30 s after a
  missed transition, which S=10 could not tell from the fixed ~10 s.

Both new checks fail against the defective versions (stall_watch.py at
47e4274 -> 30.24 s < 40; input_region.py at bf0f5c1 -> nudge sent) and pass
now, each with its passing twin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 16:02:39 +00:00
GodopuandClaude Opus 5.5 bf0f5c1014 fix(issue-30-33): review 5ec8ef33 - a multi-line codex draft is not an empty box, blocked notices reach the loop while the job runs, and both clocks are labelled on every close path
- R1 (P1): without rules the input region runs from the last prompt line to
  the end of the pane; only a last line matching the adapter's footer pattern
  (codex: `  <model> · ~/<cwd> · …`) is dropped, so a draft on a second line
  keeps the nudge withheld.
- R2: while the synchronous submit runs, a relay thread finds the job it
  registers (same session, created since the submit) and prints its
  stalled:/stall_watch: notices; before, they appeared only after the job.
- R3: the broker-unavailable disk wait that runs into the deadline is the wall
  clock: wall_clock_timeout (caller rc stays 3).
- R4: a state_change_seq change seen right before the judgement restarts the
  idle window (decision 9: S seconds of continuous idle).

Regression tests are the reviewer's reproductions, each with its passing
twin; the wrapper-level #30 tests now use this environment's broker instead
of the #18 harness's public default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 14:01:53 +00:00
GodopuandClaude Opus 5.5 47e42744e7 docs(issue-30-33): table rows for --job-timeout, MAM_JOB_TIMEOUT_S, wall_clock_timeout and the stall watcher knobs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:43:21 +00:00
GodopuandClaude Opus 5.5 622c1f8a9d feat(issue-30): a stall watcher that only notifies
lib_py/stall_watch.py runs beside the wrapper's subscriber for a local herdr
job (started after delivery, stopped when the subscriber ends; MAM_STALL_WATCH=0
turns it off). It follows `herdr agent wait` transitions and publishes only
`progress` events through one publisher; it has no path that closes a job.

- blocked: `stalled:blocked <detection line>` once per episode, never answered.
- idle for MAM_STALL_IDLE_S (600) with no background descendants of the agent
  pid (pane process-info + /proc; adapter resident_children and the heartbeat
  timer excluded) and an empty input box: `stalled:idle` and one nudge.
- input box judged from adapter facts measured on herdr 0.9.1 (codex '›',
  grok frame '│', opencode '┃' block + stop pattern); the whole-pane fallback
  is gone. No facts: withheld + `stall_watch:input_check_unavailable` once;
  never empty for 3 x S: `stall_watch:input_never_empty` once.
- herdr failures are not "no event": warning + `stall_watch:herdr_unavailable:
  <code>` (or agent_not_found), backoff, rc 2 after MAM_STALL_HERDR_FAIL_S
  (120); the wrapper prints the watcher's last lines.
- remote and human jobs are skipped with one log line.
- agent_status_reliable is an adapter field; false turns idle judgement off.
- the loop runner relays stalled:/stall_watch: notices once each.
- scoped_env drops a pane-inherited workspace scope for name lookups (#34 rule).

The module sits at the top of lib_py: delegation and loop may not import the
agent adapters (their sibling-import rules). Idle pane captures of the five
CLIs are fixtures; the mock herdr answers process-info in the real shape.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:43:21 +00:00
GodopuandClaude Opus 5.5 ec9586bb9b feat(issue-33): the job budget travels and the two clocks carry different labels
- wrapper: TIMEOUT falls back to MAM_JOB_TIMEOUT_S (--timeout > env > 3600);
  an invalid value warns and keeps 3600.
- loop: --job-timeout <sec> (run_loop.sh passes it on; refused in the legacy
  shell loop). The runner always hands the wrapper --timeout, so the budget
  the wrapper registers is the one its wait caps come from.
- runner: wait_for_job and the MAM_CHILD_WAIT_S default are budget + 300 s
  (MAM_WAIT_GRACE_S), today's 3900 with no option; an explicit
  MAM_CHILD_WAIT_S still wins.
- subscriber: the wall-clock budget returns 4 (RC_WALL_CLOCK), silence keeps
  2 (RC_NO_EVENT). The wrapper maps both to caller rc 2 and labels the close
  from the rc: wall_clock_timeout vs deadline_timeout; the disk-heartbeat
  grace that ends on the deadline is the wall clock too.
- registry close accepts wall_clock_timeout (engine: -> error); the remote
  runner's deadline and command-timeout details lead with wall_clock_timeout;
  the sub-job table files it CLOSED-UNCONFIRMED (first token, colon stripped).

Test expectations changed only where the contract changed: the loop's
contract_args now starts with --timeout, the subscriber's exit-code docstring
names rc 2/4, and the registry --help capture lists the new close kind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:43:10 +00:00
GodopuandClaude Fable 5.1 8ea65bb238 merge: fix/issue-34 (Issue #34: the loop waits for the creator's sub-jobs and hands reviewers their status; v4.7.4)
Reviews: authoring workspace 5 reviewers, full loop round 2 PASS + delta PASS; merge workspace round 1 @ed293dc claude/agy PASS, grok/codex NOT PASS (cancelled sub-job reason lost, remote deadline wording, stray-job rule) -> fixed in 8a9101e + e80186f; round 2 @e80186f all four PASS (identity: base ed293dc -> head e80186f, evidence review-tree). Full suite @ed293dc: 1156 passed, 3 skipped, 2 broker-latency flakes (pass in isolation); targeted suites @e80186f: 125 + 54 passed. Reports under .agents/reports/. Closes #34.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-25 07:44:34 +00:00
GodopuandClaude Fable 5.1 22586d2a01 release(v4.7.4): bump framework and eight skills to 4.7.4 in lockstep; archive PR #38 merge-side reviews
Issue #34: the loop waits for the creator job's sub-jobs, reviewers get
the sub-job table as evidence, a run with sub-jobs voids a round whose
source changed during review, and a pane caller reaches an agent of
another workspace on its own server. VERSIONS.md carries the entry; the
merge-side reports (round 1 @ed293dc and round 2 @e80186f with identity
sidecars, base ed293dc -> head e80186f, evidence review-tree) are archived
under .agents/reports/<session>/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-25 16:44:20 +09:00
GodopuandClaude Fable 5.1 e80186f560 fix(issue-34): merge review - the remote runner's deadline wording counts as an unconfirmed close
remote.py closes a job whose end it could not see with
`error --detail "deadline_timeout (remote runner)"`; the sub-job table
matched the reason exactly and filed it as a plain failure, losing the
unconfirmed count and the late-write warning (merge review, codex R2).
The kind is the first word of the reason now. Test T14 replays the
remote runner's close through the real registry and producer; it fails
without the fix.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-25 16:23:06 +09:00
GodopuandClaude Fable 5.1 8a9101ed30 fix(issue-34): merge review - a sub-job cancelled before injection keeps its reason in the review table; unattributed open jobs come with a verdict rule
- registry.get_feedback only returned the detail of the last completed or
  error event, so a sub-job the wrapper cancelled before injection
  (target_not_found, session_busy) reached the review table as a bare
  `cancelled` and its reason was lost (merge review, grok N1). get_feedback
  takes the event kinds to read (default unchanged); the table asks for
  cancelled too and shows NOT-DISPATCHED with the reason. Test T13, both
  reasons, fails without the fix.
- the [귀속되지 않은 열린 잡] line listed jobs registered without
  --parent-job but gave the reviewer no rule for them - the one case the
  table cannot show (claude K1). A rule sentence follows the line whenever
  such a job exists; without one the prompt is unchanged. T6 pins it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-25 16:19:12 +09:00
GodopuandClaude Opus 5.5 ed293dcef4 fix(issue-34): a pane caller reaches an agent of another workspace on its own server; an unseen target is not recorded as busy
- mam_herdr: for name lookups (agent / has-session / kill-session) a
  HERDR_WORKSPACE_ID that is the caller pane's own workspace (it matches
  the HERDR_PANE_ID prefix) is dropped. Live agent names are unique per
  server (herdr --skill), so this cannot address a same-named agent of
  another workspace; an id the caller chose, or one set outside a pane,
  still scopes (H-18/H-21/H-23). The generated shim is unchanged (S11).
- wrapper: a preflight that cannot see the target closes the job
  cancelled with detail target_not_found instead of session_busy; the
  loop's sub-job table counts it as NOT-DISPATCHED.
- tests: T10-T12 with the measured pane env (same-server socket, caller
  w6, target w2); T8 follows the sub-job-armed guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 01:25:48 +00:00
GodopuandClaude Opus 5.5 1d4f338b4c fix(issue-34): the stale-review guard is armed only when the run has sub-jobs
Without --worktree/--base the guard fingerprinted the source on every
loop, so a loop whose source holds untracked files that change on every
job voided each round: test_tier4_e2e scenario 5 keeps the mock herdr's
state file (rewritten on each herdr call) in its repository and failed
with rc 1 on 3fdbb62 (passed on 7a4a0c2). A loop without sub-delegation
now behaves as before (issue #34 requirement 3); with sub-jobs the guard
protects against a deadline-closed sub-job still writing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 01:25:48 +00:00
GodopuandClaude Opus 5.5 3fdbb626de fix(issue-34): review - a correction round keeps the earlier creator jobs' sub-jobs in the evidence table
round_roots was replaced by the correction job, so an implementation
job's open or failed sub-job vanished from the next review and the loop
could pass (planner-reviewer-codex-01, P2). The run's creator jobs now
accumulate; each wait still covers only the new job's tree, so the wait
cap is not paid again per round. T9 covers open / error / completed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 14:17:12 +00:00
GodopuandClaude Opus 5.5 8690ef4bd2 docs(issue-34): --parent-job, MAM_CHILD_WAIT_S, --wait-open-jobs, the sub-job verdict rule and the stale-review guard
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 13:37:05 +00:00
GodopuandClaude Opus 5.5 30c8c7ebab fix(issue-34): the loop waits for the creator job's descendants, hands reviewers the sub-job table with closing reasons, and voids a round whose source changed during review in every mode
- wait_for_job_tree at the execution, correction and compliance waits;
  bounded by MAM_CHILD_WAIT_S (3900) and MAM_JOB_TREE_MAX (50)
- [하위 위임 잡] table: OPEN / CLOSED-UNCONFIRMED (deadline_timeout,
  agent_blocked, or error with no reason) / NOT-DISPATCHED / error /
  completed, plus unattributed open jobs; omitted when there are none so
  option-less prompts stay byte-identical
- --wait-open-jobs also waits for unattributed open jobs
- stale-review guard without --worktree/--base: head + tree (control
  plane excluded) before review, re-checked after verdicts and before
  promotion; off with a warning when the source cannot be fingerprinted
- tests/test_issue34_job_tree.py: real wrapper registrations, real
  publish_event / deadline close, real loop process

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 13:37:05 +00:00
GodopuandClaude Opus 5.5 ec663a93c8 feat(issue-34): a job records its parent; submit --parent-job (MAM_JOB_ID by default) and the brief tells the agent to pass it on
- registry: parent_job field, register --parent-job fails closed on an
  unknown parent, children_of/descendants_of, list --parent
- wrapper: --parent-job; $MAM_JOB_ID default (ignored with a warning when
  this registry lacks it); brief line and one sentence at the end of the
  injected instructions
- create_session: the onboarding instructions carry the same sentence

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 13:37:05 +00:00
GodopuandClaude Fable 5.1 7a4a0c2091 merge: fix/issue-36 (Issue #36: delegated prompts travel by file, a failed delegation ends the loop with rc 1; v4.7.3)
Reviews: authoring workspace 5 reviewers x 3 rounds PASS; merge workspace round 1 @59be1c8 claude PASS, agy/grok/codex NOT PASS (S9 macOS wording, runner prompt-file write failure, empty --prompt-file) -> fixed in 708262a; round 2 @708262a all four PASS (identity: base 59be1c8 -> head 708262a, evidence review-tree). Full suite @59be1c8: 1139 passed, 2 skipped, 1 failed (S9, fixed); targeted suites @708262a: 125 passed. Reports under .agents/reports/. Closes #36.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-24 09:10:39 +00:00
GodopuandClaude Fable 5.1 c3e6a7e96d release(v4.7.3): bump framework and eight skills to 4.7.3 in lockstep; archive PR #37 merge-side reviews
Issue #36: delegated prompts travel by file, a delegation that fails ends
the loop with rc 1. VERSIONS.md carries the entry; the merge-side reports
(round 1 @59be1c8 and round 2 @708262a with identity sidecars, base
59be1c8 -> head 708262a, evidence review-tree) are archived under
.agents/reports/<session>/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-24 18:10:28 +09:00
GodopuandClaude Fable 5.1 708262a598 fix(issue-36): merge review - a prompt file the runner cannot write is a failed submit; S9 asserts the libc wording on Linux only; an empty prompt file is refused
- runner._run_cmd: the write of the prompt file was outside the cleanup
  and error handling that the exec had (merge review: claude H1, codex R1).
  A failed write now removes the partial file and returns the same 126
  failed submit an unstartable wrapper does, so the loop ends with rc 1
  and no traceback. Test S4c.
- test S9 required the literal "File too large" in stderr; bash 3.2 on
  macOS prints nothing for EFBIG, so the whole suite failed on this host
  (agy, grok, codex R2). The contract asserted everywhere is rc != 0, no
  leftover file and the portable diagnostic line; the libc wording is
  checked on Linux only.
- the wrapper accepted an empty --prompt-file and registered an empty
  prompt (claude H3); it is refused now. Test S1e.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-24 17:55:56 +09:00
GodopuandClaude Opus 5.5 59be1c8920 fix(issue-36): create_session hands the agent a brief path, not the task; a prompt file that cannot be written is removed
Review of f26952c (codex, job cc849cbf):

- P2: create_session registered the task by file but then pasted the whole
  task into the onboarding text, which send_keys_safe hands to `herdr agent
  prompt` as one argv string. A 130,500-byte --submit-job was registered
  intact and never reached the agent (execve E2BIG). The task now goes into
  <jobs>/<id>/brief.md, as the delegate-job wrapper does, and the injected
  text names that path. The publish command and its dispatch token (#23) are
  unchanged. The other send_keys_safe callers (the wrapper's short pointer,
  stop_session's exit key) carry no task text.
- P3: delegate_job_safe's write-failure branch left the partial prompt file
  behind and logged to stdout, which callers capture. It now removes the file
  and logs to stderr.

S8 (real create_session.sh, 100 and 130,500 bytes, strace when present) and
S9 (the real function, RLIMIT_FSIZE write failure) carry the reviewer's
reproductions over; all three fail on f26952c.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 07:02:20 +00:00
GodopuandClaude Opus 5.5 f26952ccc3 fix(issue-36): the legacy shell loop and lib.sh's submit helpers pass prompts by file
The two remaining exec points that carried a prompt as one argv string
(capped at 131,072 bytes):

- run_loop.sh (MAM_LOOP_PY=0): delegate_job_safe, which all eleven shell
  delegations go through, turns `--prompt <text>` into `--prompt-file` under
  the control root's .mam/ and removes the file once the wrapper returns. Its
  failure lines go to stderr: callers capture stdout, and under set -e a
  failed submit ends the loop at that line with the wrapper's code, so the
  cause was being swallowed.
- lib.sh delegate_submit_job and its legacy twin (create_session's onboarding
  and --submit-job): the prompt is written under .mam/ and handed on as
  --prompt-file; lib_py.delegation.cli submit accepts --prompt-file and
  forwards it to the registry. --prompt keeps working everywhere.

S6/S6b push a 140,000-byte prompt through the CLI and both helpers; S7 runs
the real run_loop.sh with a 140,000-byte plan and a 150,000-byte diff; S7b
fixes a nonzero exit when the implementation or compliance job cannot be
registered. All six fail on 14d11bf.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 06:08:25 +00:00
GodopuandClaude Opus 5.5 14d11bf062 fix(issue-36): a job that cannot be registered after a rebuttal ends the loop with rc 1
Review of f5cadd9 (planner-reviewer-codex-01, R1): the compliance job's
registration failure was only logged, so a passing next round ended the
real loop process with rc 0. Compliance now returns 1 when it cannot be
registered or does not finish, as the correction step already does.
Re-adjudication and arbitration also return 1 when their job cannot be
registered; a registered job that yields no verdict keeps the designed
fail-closed fallback.

S4c (stubbed delegate_job, a fixture whose later rounds never pass) is
replaced by S3c: a real `python -m lib_py.loop` process whose wrapper runs
an executable that simulates the agents, refusing the compliance,
re-adjudication or arbitration registration while the next round passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 03:38:10 +00:00
GodopuandClaude Opus 5.5 f5cadd98c0 test(issue-36): prompts over the argv cap go through the real wrapper, registry and loop; a failed delegation ends the loop with rc 1 and no traceback
S1-S1d push a 140,000-byte prompt through the real wrapper and registry by
file, with a negative control that the same prompt as an argument fails with
E2BIG. S2 runs the real runner against a recording wrapper. S3/S3b run a real
`python -m lib_py.loop` whose wrapper fails or cannot start. S4/S4b/S4c cover
the exec-error handler, the busy retry and the compliance log. S5 sends a
collected diff from the former 131,072..200,000-byte blind spot through whole.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 02:30:13 +00:00
GodopuandClaude Opus 5.5 c77806b1b1 fix(issue-36): the loop passes every delegated prompt as a file; a wrapper that cannot start is a failed submit, on the first try and on the retry
delegate_job writes the prompt to a temp file under the control root's .mam/
and hands the wrapper --prompt-file; the file is removed once the wrapper
returns. An OSError from starting the wrapper becomes a failed submit (rc 126)
that the existing error branch logs, so the session-busy retry no longer
dereferences a missing result. A retry that yields no job id, and a
compliance job that cannot be registered, are now logged instead of silent.

The collector caps are documented as the size of the evidence a reviewer is
shown; delivery has no cap of its own any more. The loop skill notes that a
pipeline hides the loop's exit code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 02:30:13 +00:00
GodopuandClaude Opus 5.5 68a5d31381 fix(issue-36): registry and wrapper accept --prompt-file; the wrapper hands the same path on
One argv string is capped at MAX_ARG_STRLEN (131,072 bytes on Linux), so a
prompt passed as --prompt could not reach the registry once it carried a
large diff. submit now takes --prompt-file and passes the same path to
`registry.py register --prompt-file`; the loop type's per-iteration update
goes by file too. --prompt keeps working unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 02:30:13 +00:00
GodopuandClaude Fable 5.1 1debc7d924 merge: fix/issue-23-24 (Issues #23, #24: cross-server herdr scope, agent-agnostic start-up dialogs, started-based onboarding verdict, verified rollback; v4.7.2)
Reviews: authoring workspace 5 reviewers x 5 rounds PASS; merge workspace round 1 @f4ba205 claude/agy/grok PASS (codex report lost to host sleep; its two probes fixed in 05b0ea4), round 2 @05b0ea4 all four PASS (identity: base f4ba205 -> head 05b0ea4, evidence review-tree). Full suite @f4ba205: 1111 passed, 1 skipped; affected suites @05b0ea4: 123 passed. Reports under .agents/reports/. Closes #23, #24.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-23 23:36:37 +00:00
GodopuandClaude Fable 5.1 9fb037c084 release(v4.7.2): bump framework and eight skills to 4.7.2 in lockstep; archive PR #35 merge-side reviews
Issues #23 and #24: cross-server herdr scope, agent-agnostic start-up
dialogs, onboarding delivery judged by the agent's own started event,
verified rollback. VERSIONS.md carries the entry; the merge-side reports
(round 1: claude/agy/grok; round 2 on 05b0ea4: all four, with identity
sidecars, base f4ba205 -> head 05b0ea4, evidence review-tree) are archived
under .agents/reports/<session>/. The round-1 codex report was lost to the
host sleeping mid-review; its two probes are recorded in the changelog.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-24 08:36:35 +09:00
GodopuandClaude Fable 5.1 05b0ea4762 fix(issue-23-24): merge review - the environment form of the auto-trust switch reaches the handler; a rate-limited dialog does not hide the next one
Two probes from the merge-side codex review (its report was lost to an API
outage; the probes and their results were recovered from its transcript):

- create_session.sh and resume_session.sh set AUTO_TRUST=1 and then
  exported MAM_AUTO_TRUST from it, so the documented MAM_AUTO_TRUST=0
  without a flag reached handle_startup_dialogs as 1: the operator had
  turned automatic trust off and it stayed on. Now AUTO_TRUST defaults to
  the environment value and an explicit --auto-trust/--no-auto-trust wins.
- _mam_dialog_pick exited on the first dialog inside its resend cooldown
  instead of looking further down the screen, so a skipped (--no-auto-trust)
  or just-answered trust prompt hid the codex update notice below it, which
  the docs say is always answered. It continues to the next dialog now; S5
  and S5b (one send per cooldown, one retry after it) still hold.

Regressions: S6c, S5f (startup path), R3b and C3 (resume/create, env only,
flag over env). Each fails without the fix. The create skill doc names the
environment form and its precedence.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-24 06:47:57 +09:00
GodopuandClaude Fable 5.1 f4ba205c9f fix(issue-23): the remote audit record names the dispatch it vouches for; a record without identity fails closed
Review round 4 (planner-reviewer-claude-01 B-1, planner-reviewer-codex-01
P2, reached independently): dispatch_verified is a boolean. It says the
subscriber's token check ran when the record was written, but names no
dispatch, the token being masked by the audit redactor and attempt_id not
written. events.ndjson is appended to across a job's attempts, so a started
verified under attempt 1 satisfied every later attempt's wait - the very
misattribution R1 forbids, alive on the remote path while the local path
(data.dispatch_token in events.log) was already immune. codex reproduced it
with the real signed producer, the real _Watcher, the real audit writer and
the real operator re-dispatch; both waits answered 0 where 1 was due, even
when attempt 2's subscriber had correctly dropped attempt 1's replayed packet.

The subscriber now writes dispatch_fingerprint = SHA-256 of the token it
verified, and attempt_id for readers. With a token demanded, the predicate
accepts an audit record only if it is dispatch_verified AND its fingerprint
equals SHA-256 of that token; a record with no fingerprint - written before
the field existed - names nothing and is rejected on that path. Without a
token, any received started counts, as before.

Fingerprint over attempt_id as the identity, weighing the two proposals: the
local path already compares the token, so hashing it keeps one identity axis
for both sources; the token is a uuid4 nonce (registry.py:141), 128 bits, so
its SHA-256 is not recoverable and it is a CAS fence rather than the HMAC
secret in the first place; and attempt_id cannot tell apart a token
re-minted within one attempt, where the token can. attempt_id is recorded
because it is cheap and readable, not relied on.

W7 pins codex's five cases end to end - current dispatch accepted, an old
attempt's record rejected after a real re-dispatch, still rejected when the
stale packet is replayed and dropped, the new dispatch's own started
accepted, a bad HMAC never recorded - with create's wait and the wrapper's
wait required to agree on each. W8 pins the fail-closed legacy record and
the untokened compatibility path. W5 gains the no-fingerprint and
other-dispatch cases; W6 checks the real _Watcher writes the fingerprint and
never the token. S7e's status reset had no reason left and is gone. codex's
own reproduction file, unmodified, passes 5/5 against this tree.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 17:52:51 +00:00
GodopuandClaude Fable 5.1 087eacaf21 fix(issue-23): both ack waits share one contract - the wrapper's status bypass is gone; the G9 guard removes only the docstring node
Review round 3 (planner-reviewer-codex-01, reviewer-opencode-01): R1 blocking,
R2 and the "ONE builder" wording non-blocking. All three reproduced; none
rebutted.

R1. _wait_started still returned 0 as soon as the record left `pending`,
ahead of the shared predicate, so `error`, `cancelled` and another dispatch's
`running` all acknowledged an injection that create's delegate_wait_started
rejects - the two contracts the ack-finding brief said must not diverge. Both
reviewers reproduced it with the real producer and no record manipulation;
my S7n had hidden it by resetting the record to `pending`.

The bypass and the console grep are gone. The wrapper's evidence is now
delegate_started_recorded alone, the same call create makes. For a remote
agent - no shared disk, so no line in events.log - the predicate reads the
subscriber's audit record of the `started` it received. That record is
attributable by construction: the subscriber verifies the HMAC and drops any
event whose data.dispatch_token differs from the job's expected token before
it writes anything. The token itself cannot go into the record (the audit
redactor masks token-named keys), so the subscriber now stamps a boolean
dispatch_verified, and with a token demanded only a verified record counts;
an untokened legacy record fails closed. The registry status is never
evidence; a terminal job is preserved by the terminal logic, not turned into
an acknowledgement.

Tests: W1/W2/W3 are the reviewers' three negatives and the positive, real
producer, record left exactly as it produced it; W4 runs create's wait and
the wrapper's wait side by side on all four and requires the same answer;
W5 pins the remote route (a verified record counts, an unverified or
non-started one does not); W6 drives the real _Watcher and shows it stamps
the flag on a matching started and records nothing for a mismatch; S7n no
longer resets the record. test_issue14's "registry ack over unconfirmed
inject" produced its ack with a bare status flip - the very bypass - and now
produces it the way an agent does, publish_event with the dispatch token;
its scenario and every other assertion are unchanged. test_issue18's remote
e2e accepted the status messages and now expects the shared one.

R2. _code_only blanked a docstring's whole line range, so code sharing that
line - `"""doc"""; call()` - vanished from the scan. It now removes only the
docstring node from the AST and unparses the rest (comments drop out with
it, code strings stay); test_g9b pins the four same-line shapes.

The builder comment claimed one builder for create and the wrapper; the
wrapper keeps _pub_cmd and S7l pins the two equal argument-for-argument. The
comment now says that.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 16:43:00 +00:00
GodopuandClaude Fable 5.1 4805fd4511 test(issue-5): the G9 no-frozen-helper guard reads code, not comments or docstrings
The full suite on 1667fda showed exactly one failure beyond this host's
baseline: test_g9_phase2a_spawns_no_daemon_and_calls_no_frozen_helper. Its
`\blib\.sh\b` pattern matched a docstring in watcher.py that points at the
shell twin of caller_server ("Same convention as lib.sh's
_herdr_caller_server") - a mention written so the two implementations stay in
sync, not a call.

The guard's own docstring states its claim: no frozen lib.sh helper is
*invoked* from lib_py/loop. Its one prior change (803ba43) added the word
boundary after `hashlib.sha256` tripped it - the same class of false positive,
handled by narrowing the matcher rather than by contorting innocent code.
This continues that: the three patterns and the word boundary are unchanged,
but they now run over the source with comments and docstrings blanked. Every
way Python could actually reach a lib.sh helper - a path or `source` in a
subprocess argv, an identifier - lives in code text, which is still scanned in
full, so the guard keeps its reach. test_g9b pins both sides: an
invocation-shaped string in code still trips it (lib.sh and
ensure_subscriber_daemon both reported), a bare lib.sh path in a list literal
still trips it, and the same words in a module docstring, a function
docstring and a comment do not - with hashlib.sha256 present as the original
regression.

Rephrasing the docstring to dodge the literal was the other option and was
not taken: it would hide a useful cross-reference to satisfy a matcher, which
is the evasion shape this guard should not encourage, and it would leave the
next honest comment to trip the same wire.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 15:32:40 +00:00
GodopuandClaude Fable 5.1 1667fda8dd fix(issue-23): the onboarding ack is satisfied by what a real agent publishes; one command builder and one started predicate for create and the wrapper
Trial 2 of this branch: cross-server delivery was restored (f7cee00), the
agent completed its onboarding and published started and completed - and
create_session still waited 180 s, called the onboarding unconfirmed, and
tried to write `error` over the completed job (refused by the registry).

delegate_wait_started required the started line to carry data.dispatch_token
(review R1: a stale attempt's started must not vouch for this one). But
publish_event only writes that field when its command line carries
--expect-dispatch-token, and create_session's hand-rolled onboarding command
never did - the wrapper's _pub_cmd does, which is why every wrapper-dispatched
agent's started has the token and no create_session onboarding ever could.
Five reviewers missed it because the fixture wrote the token into the started
line by hand: a fake producer that differs from the real one.

Fix, along the wrapper's existing mechanism rather than a new one:

* delegate_publish_cmd (lib.sh) builds the publish command for an agent, with
  --expect-dispatch-token and --attempt-id, and create_session uses it. The
  agent's own started now names the dispatch, and publish_event's CAS check
  refuses a stale dispatch's event at the source. S7l pins the builder
  argument-for-argument against the wrapper's _pub_cmd.
* delegate_started_recorded (lib.sh) is the one definition of "this
  dispatch's started is on disk" - JSON-parsed, token-matched when a token is
  given - used by delegate_wait_started and by the wrapper's _wait_started in
  place of its regex. The wrapper keeps its status and subscriber routes
  (remote agents); the shared part is the evidence contract.
* An already-terminal job is left alone when the wait expires (req. 4): no
  error over a completed/error/cancelled record.

The fixture now registers the record through registry.py and publishes
started through the real publish_event.py; the only synthetic line left (S7h)
exists to pin key-order independence and says so. S7i pins the trial-2 defect
as the negative it is (old command shape -> started without token -> not
acknowledged); S7j runs the builder's command through the real producer and
is acknowledged on the first probe; S7n shows the wrapper accepting the same
real line and rejecting a stale token; S7m pins no-write-over-terminal. S7f
moves from "publish error" to "leave the terminal job alone", per req. 4.

Option B (verify data.hmac_sig with the job's auth_token, plus record-token
still equal) was weighed and not taken: verify_hmac returns True with no
auth_token - PoC mode, which the trial and every sandbox run in - so it proves
nothing there; in production hmac_sig authenticates the signer but does not
identify the dispatch, so R1 would rest on a current-state check that a stale
line plus an unrotated token passes; and it would diverge from the wrapper
unless the wrapper changed too. Token exposure is not new: every wrapper-
dispatched instruction already carries the dispatch token in the pane, and it
is a per-dispatch CAS nonce, not the HMAC secret.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 14:42:38 +00:00
GodopuandClaude Fable 5.1 f7cee00dec fix(issue-23): the caller's server comes from HERDR_SOCKET_PATH, not only HERDR_SESSION
The live trial of this branch found the cross-server verdict inert on this
host: _herdr_call_is_cross_server read the caller's server from HERDR_SESSION,
and herdr 0.8.2 does not export that variable to a pane of the default server
- the orchestrator's usual home, and exactly the pane the #23 leak comes from.
Every call was judged "not crossing", the three ids were never dropped, and
all four trial onboardings ended "no started within 180s". My own pane sits in
a named server and does carry HERDR_SESSION, which is how the assumption got
past me.

HERDR_SOCKET_PATH is exported to every pane and its layout names the server:
.../.config/herdr/herdr.sock is the default server and
.../.config/herdr/sessions/<name>/herdr.sock a named one - the convention
`herdr session list --json` reports and the shim already uses to find a named
server's socket. _herdr_caller_server prefers HERDR_SESSION when a herdr does
export it, otherwise reads the socket's shape (suffix-matched, not $HOME-
anchored), and yields "" for anything unrecognised so an exported scope is
respected rather than guessed away. The rest of the rule is unchanged from
3bcaa39: a `default` or empty target is the ambient server, no caller server
means no pane, caller == target is the same server. watcher.caller_server
applies the same rule for the loop's own herdr calls.

Reproduced against the live server in the trial's exact env: 10a678c answers
"NOT crossing / has-session NOT FOUND"; this commit answers "caller server
default / crossing / FOUND". S1c pins that env end to end with the recorder
asserting the ids never reach herdr; S1d-S1h pin the verdict, the named-socket
same-server and cross-server cases, HERDR_SESSION precedence, and the
unrecognised layout. The 3bcaa39 tests are unchanged and still pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 14:13:38 +00:00
GodopuandClaude Fable 5.1 10a678c010 fix(issue-23-24): the dialog handler sees the ready banner on its own; only this dispatch's started is delivery evidence
Review round 1 (planner-reviewer-claude-01, reviewer-agy-01,
planner-reviewer-codex-01): one blocking defect and two P2s, all confirmed.

B-1. handle_startup_dialogs took its ready tokens only from the environment,
which wait_for_tui_ready fills when it evaluates the adapter facts. Called on
its own - as resume_session.sh calls it, with no readiness wait before it -
it had no tokens, its early-return branch could never fire, and every resume
of every agent idled through the full 20 s timeout (base: 0.03 s). rc was 0
throughout, so nothing failed; the resume tests just took ~30 s each.
_mam_load_dialog_facts now also takes MAM_STRONG_READY_TOKENS / MAM_READY_TOKENS
from the facts, but only when the caller has not set them, so a caller-set
value (wait_for_tui_ready's own, or a test steering readiness) is never
clobbered - the concern that made it take a single fact in the first place.
S5d pins one-iteration return on a ready first frame for five adapters with
no environment; S5e pins that caller tokens survive; R4 gets a wall-clock cap
so a timeout-idle can no longer pass as success. The old
_MAM_READY_TOKENS_CLAUDE fallback was left defined but unread - removed.

R1 / N-2. delegate_wait_started treated any status past `pending` as the
agent's acknowledgement. codex reproduced three false positives: `error` and
`cancelled` records with no started line, and a `running` record from another
dispatch. The status branch is gone; the evidence is a started line carrying
this dispatch's token, which the local publisher writes before it syncs the
status, so nothing that really started is missed (S7c re-pinned on the line
with running/completed/error statuses; S7f/S7g pin the three negatives).

N-4, taken with it: the events.log line is parsed as JSON rather than pattern-
matched. publish_event nests dispatch_token under `data`, and the old regex
only matched because of key order (S7h, real line shape and a reordered one).

R2 / N-1. Two tests hardcoded <repo>/.venv/bin/python and failed with
FileNotFoundError in every review worktree. sys.executable.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 10:25:52 +00:00
GodopuandClaude Fable 5.1 0dfbb2cc8a fix(issue-24): resume answers start-up dialogs for every agent, with the same switch as create
resume_session.sh called handle_startup_dialogs only when AGENT=claude and
passed no agent, so a resumed agy, grok or codex sat on its directory-trust
prompt exactly as the create path did before this branch. The gate is gone and
the agent is passed, which is the call create already makes.

--no-auto-trust / MAM_AUTO_TRUST apply to resume too. A resumed CLI shows the
same trust dialog as a fresh one, in the same directory, with the same
consequence for codex (trusting loads the project's local config, hooks and
exec policy), and handle_startup_dialogs already reads MAM_AUTO_TRUST - so the
only question was whether resume exposes the switch or silently inherits the
caller's environment. Exposing it keeps the two skills' contracts identical.

The tests run the shipped create/stop/resume scripts against the mock herdr.
The mock's pane always shows a ready banner, so they fix the call resume makes
(session, timeout, agent, MAM_AUTO_TRUST) by overriding the handler in the
sandbox copy of lib.sh, plus one unmodified run showing the real handler is
reached for a non-claude agent; which keys a dialog gets is fixed on the create
side (S4-S6).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 08:19:18 +00:00
GodopuandClaude Opus 5 ce802d21c8 docs(issue-23): the create skill documents the preserve-on-unacknowledged-onboarding rule and MAM_ONBOARD_ACK_S
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 06:24:33 +00:00
GodopuandClaude Opus 5 33b91fe0c8 fix(issue-23): an unacknowledged onboarding marks the job and keeps the session
Two defects in the started-based verdict, both found by the full suite.

The first was destructive. No `started` within T exited 1, which fired the
rollback trap and killed the session. That is worse than the bug being fixed:
create_session already has a rule for "the agent is not responding but the
process is alive" one step earlier - a TUI that never signalled ready is
preserved for diagnosis - and an onboarding that is merely slow is the same
situation. The job is marked `error` and warned about loudly; the session, its
row and the pane stay. MULTI_AGENT_RULES §6 has the GM gate on this job
anyway, so an unacknowledged onboarding still blocks real work.

It also broke 21 tests across 8 modules (create/sanity/tier2/tier3/tier4/
issue15/issue19/issue21) and added roughly an hour to the suite: no sandbox
has an agent to publish `started`, so every create_session test sat out the
full 180 s and then failed on the rollback. MAM_ONBOARD_ACK_S shortens that
wait, and the sandbox fixture sets 3 s. It is deliberately not the wrapper's
MAM_INJECT_ACK_GRACE_S - that is a different wait whose own tests assert on
the value they pass, and reusing it made issue18's acceptance2 read "within
3s" instead of "within 4s".

The second: the delivery failure published `error` and then the rollback
published `cancelled` on the same job, which the registry refuses
("conflicting cancelled after terminal error"). Terminal events are published
exactly once, so the trap now skips a job this run already closed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 06:24:06 +00:00
GodopuandClaude Opus 5 2cd20423ac docs(issue-23-24): the create skill documents --no-auto-trust, the started-based onboarding verdict and exit 43
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 04:28:57 +00:00
GodopuandClaude Opus 5 123dca47bb fix(issue-23): onboarding delivery is judged by the agent's own started event, and a rollback verifies the kill
Plan commits 3 and 4 landed together: both rewrite the same region of
create_session.sh and share one test file, and splitting them afterwards would
have meant hunk-level surgery on already-verified code.

Delivery verdict (item 3). create_session --onboard called a healthy agent a
failure three seconds after registering the job: inject_instructions ->
send_keys_safe returns rc 4 when `agent prompt --wait --until working` fails
and the pane is still not working two seconds later, which is exactly where a
TUI that is still starting up lands. It never got the ISSUE-18 confirmation the
delegate-job wrapper has. rc 4 is a warning now and the agent's own `started`
decides, within the job's own ack timeout; rc 1/2/3 stay hard failures, since
they say the pane never quiesced, a dialog was blocking, or the text never
appeared, and no later `started` repairs those.

The self-published `started` had to go first. create_session published it on
the agent's behalf right after injection, so the registry showed a started job
whether or not the agent ever saw the prompt - with that line in place, no
started-based verdict can mean anything.

delegate_wait_started is lib.sh's counterpart to the wrapper's _wait_started
(plan item D10-b). The wrapper's own `submit` was the first choice, but it
blocks until the job reaches a terminal state, and create_session must return
as soon as delivery is confirmed; the on-disk evidence it reads - registry
status past `pending`, or a started line carrying this dispatch token - is
written by publish_event before its wire publish, so no broker is involved.

Rollback integrity (item 4). The trap ran `_herdr kill-session -t
"$SESSION_NAME" 2>/dev/null || true`, so a kill that closed nothing still
printed "rolling back" while the session kept running, and the row stayed
`running` pointing at the job the same rollback had just failed (#23 symptom
5). With item 1 in place this check is finally trustworthy: the kill used to
fail because the caller's workspace id hid the pane.

The trap now reads the kill's rc, confirms with has-session, closes the
onboarding job as `cancelled` (a terminal transition the registry knows, and
the truthful one - the job was never dispatched), and marks the row. A session
still alive exits 43 with the manual cleanup command, distinct from 42 (TUI
never ready).

The row keeps a schema-valid status rather than the plan's `error`, which
atomic_yaml.py rejects: `terminated` once the session is really gone, and
`running` when the kill failed, because it is still running. What makes that
case legible is the note, the cleared delegate_job_id and exit 43 - not a
status claiming the opposite of the truth. The row is never deleted; a deleted
row takes the evidence with it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 04:28:27 +00:00
GodopuandClaude Opus 5 5ba04a27ca feat(issue-24): startup dialogs come from the agent adapter and are handled while waiting for the TUI
handle_startup_dialogs was a claude-only string ladder called only when
AGENT=claude, and wait_for_tui_ready does not treat a dialog screen as ready.
So agy, grok and codex sat on their directory-trust prompt for the full
readiness timeout, the session was recorded as tui-ready-timeout, and
onboarding injection was disabled (create_session.sh:313-314) - a human had to
accept each dialog by hand.

The signatures and accept keys move to the adapters as `startup_dialogs`
(pattern, keys, kind), published through the existing `facts` eval contract as
MAM_STARTUP_DIALOGS. claude's ladder moves over verbatim; agy takes Enter, grok
y, codex Enter, and codex's update notice takes 2 (Skip), which blocks a start
just as hard while changing nothing. kind is `trust` or `notice`:
--no-auto-trust leaves trust dialogs to a human and still answers notices,
since trusting a directory makes codex load its project-local config, hooks and
exec policy.

Re-sending is gated by time (MAM_DIALOG_RESEND_S, default 5s), not by frames.
#24's comment reports that `--source visible` keeps showing an accepted dialog
for a while, and _pane_capture hardcodes that source, so "still on screen" is
not evidence the keys were missed; acceptance is judged by the ready tokens
alone.

Two existing tests asserted the old shape and are updated, not weakened:
b19's "Escape branch stays" now looks for it in claude's adapter, and c1b's
"Enter once per iteration" becomes "never twice for one frame", which the
cooldown satisfies strictly more tightly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 04:22:09 +00:00
GodopuandClaude Opus 5 3bcaa39ab0 fix(issue-23): a herdr call to another server drops the caller pane's workspace/pane/tab ids
A MAM script often runs inside one herdr server's pane (the orchestrator's)
while targeting another (the isolated MAM server). The pane exports
HERDR_WORKSPACE_ID, HERDR_PANE_ID and HERDR_TAB_ID, and those ids only mean
something on the server that issued them. They reached the scope filters as a
foreign workspace, whose fail-closed miss (ISSUE-3 / F-2b) reports a live
session as absent: has-session, kill-session, agent prompt and the delegate-job
preflight all answered "no such session". Reproduced on Linux and macOS in the
#23 comments, worked around until now with `env -u` at every call site.

mam_herdr drops the three ids for a call aimed at a named server other than the
caller's own. Scope then falls back to .mam/herdr_workspace_id and finally to
server-global, which is what _herdr_ws_scope already does. `default` is not a
crossing: it is the sentinel for "pass no session flag", so herdr answers on
the ambient server, which for a caller in a pane is that caller's own. A caller
outside any pane keeps an exported scope, so H-18/H-21/H-23 keep their meaning.

The fix lives in mam_herdr, not in the shim heredoc: that heredoc is
materialised into $WORKSPACE_ROOT/.mam/shim/herdr, one file shared by every
checkout bound to a control plane and rewritten on every herdr call, so editing
it would put unreviewed code in the control root and let two checkouts
overwrite each other. S11 fixes that invariant.

lib_py/loop/watcher.py builds its own herdr argv and is the one path that does
not go through mam_herdr, so it applies the same rule via scoped_env.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 04:14:45 +00:00
GodopuandClaude Fable 5.1 1e78dae6eb merge: fix/issue-25 (Issue #25: promoted plans are reused only with --plan-file; empty plan reports are not silent; v4.7.1)
Reviews: authoring workspace round 2 @1e82651 5/5 PASS; merge workspace @1e82651 claude/agy/grok/codex 4/4 PASS (identity: base 6fc9194 -> head 1e82651, evidence review-tree). Full suite @1e82651: 1037 passed, 1 skipped. Reports under .agents/reports/. Closes #25.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-21 15:38:21 +00:00
GodopuandClaude Fable 5.1 6b7198f73c release(v4.7.1): bump framework and eight skills to 4.7.1 in lockstep; archive PR #32 merge-side reviews
Issue #25: a promoted plan is reused only with --plan-file; the implicit
newest-plan load is gone in both runners, and an empty planner report no
longer passes silently. VERSIONS.md carries the entry; the four merge-side
review reports and their identity sidecars (base 6fc9194 -> head 1e82651,
evidence review-tree) are archived under .agents/reports/<session>/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-22 00:38:10 +09:00
GodopuandClaude Opus 5 1e826515cc fix(issue-25): a relative --plan-file is taken against the control root, as its usage line says
Review R1/N1 (reviewer-grok-01, planner-reviewer-codex-01), reproduced before
fixing. The usage line and SKILL.md both promise "relative to the control
root", but parse_args did Path(...).expanduser().resolve(), which is relative
to the caller's cwd. The runner never chdirs to workspace_root, so the two are
routinely different - the documented recipe runs the loop from the control root
while the source root is a worktree.

Two ways it went wrong: a plan that exists only under the control root was not
found at all (exit 1), and a same-named file in the caller's cwd was loaded
instead, quietly putting an unrelated plan into the first implementation
prompt. That second one is the very failure #25 is about, reintroduced through
the option meant to replace it.

Resolve against workspace_root the way jobs_dir already does (Issue #21 R2);
pathlib's `/` leaves an absolute or expanded-home path untouched. Every earlier
test passed an absolute tmp_path, which is why none of them caught this: P3c
covers both failure modes with the control root away from the caller's cwd, and
P3d pins that absolute and ~ paths are not rewritten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 05:17:50 +00:00
GodopuandClaude Opus 5 5fa592347c docs(issue-25): the loop skill documents --plan-file and the empty-report handling
The skill described the implicit load as a feature in five places. Replace them
with what the loop does now: nothing is loaded without --plan-file, the path is
relative to the control root, and the plan rides the first implementation
prompt only. Adds the CLI table row and a section on what an empty plan report
means at each of the four load points.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 03:26:44 +00:00
GodopuandClaude Opus 5 a334083760 test(issue-25): plan reuse is explicit only and empty plan reports are not silent (P1-P11)
P1 is the reproduction: a running planner plus a promoted plan in its report
directory must not reach the Creator. P2 pins that a named plan is used, that
the newer unrelated one is not, and that the plan rides only the first
implementation prompt - the correction round carries feedback alone, as the
implicit load did too. P3-P5 keep the option's checks in front of any job.
P6 keeps --plan mode and its promotion working.

P9-P11 cover the loads the review widened: an empty initial plan stops the run,
an empty discussion refine keeps the previous plan, and an empty escalation
refine leaves the "follow the updated plan below" prompt carrying the real one.
The existing _drive helpers hand every job the same non-empty PASS report and
so cannot reach these paths at all, so this module drives the loop with its own
stub that scripts a report per job. P7-P8 pin wrapper/runner parity, the legacy
shell refusal, and that neither implementation globs for a plan any more.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 03:26:44 +00:00
GodopuandClaude Opus 5 e551e3baf1 fix(issue-25): one plan-text check for every load; an empty planner report stops the loop, an empty refine keeps the plan
The --plan-file check landed on the one path a human is watching. The three
--plan-mode loads read a report written by another agent with nobody watching,
and had no content check at all: find_report only tests that the path exists.

An empty or whitespace-only report therefore blanked current_plan in silence.
Phase 2's `if self.current_plan` then flipped the prompt to self-planning and
the run finished rc 0, having paid for a planner job, a Creator challenge and a
refine job and thrown the result away. The escalation refine was worse: it has
no guard, so the correction prompt asserted "follow the updated plan below" and
carried nothing.

All four loads share _plan_text() now - a plan is a readable regular file with
text in it - and each caller says what None means. An empty initial plan stops
the run instead of demoting it, and an empty refine warns and keeps the plan it
already has, which is still good. Those two together leave the escalation
prompt unable to claim a plan it does not carry, so it needs no new branch.

Found by the Creator's challenge to the plan (job 0f63ac1d), sustained by the
Planner as revision 1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 03:26:34 +00:00
GodopuandClaude Opus 5 a28711cccf fix(issue-25): reuse a promoted plan only with --plan-file; drop the implicit newest-plan load
Without --plan, Phase 1 resolved a planner session automatically and globbed
<workspace_root>/.agents/reports/<planner>/plan-*.md for the newest file by
mtime, handing it to the Creator as "the plan already agreed on". Both keys are
wrong: the report directory belongs to a session, not to this task, so other
issues' plans pile up in it, and right after a clone or checkout mtime is
checkout order. 47 such plans are committed in this repository, so an ordinary
run could silently implement against an unrelated design.

Reuse is explicit now. --plan-file <path> is the only way to load an existing
plan; it is resolved against the control root, checked before a single job
exists, and refused if the file is missing, not a regular file, unreadable or
empty (an empty file would quietly turn into a "no plan" prompt). It cannot be
combined with --plan, and --review-only warns and ignores it because no
implementation job is delegated there. The implementation prompt itself is
unchanged, so the Creator's contract does not move.

Phase 1 now says which plan it loaded and where from, or that it loaded none.
The legacy shell loop (MAM_LOOP_PY=0) loses the same auto-load and rejects
--plan-file rather than duplicating the check on a path that is on its way out.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 03:26:24 +00:00
GodopuandClaude Fable 5.1 6fc9194f89 merge: feat/issue-21-items-4-5-7 (Issue #21 phase 2: worktree helpers, review identity, acceptance A1-A8; #20 test isolation; #27; #28 host portability; v4.7.0)
Reviews: round 1 @46ea4cc claude/agy/grok PASS, codex NOT PASS (R1-R3) -> fixed in 08db1da; delta review @08db1da claude/agy/grok PASS, codex @80a5614 PASS. Reports archived under .agents/reports/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-20 21:43:39 +09:00
GodopuandClaude Fable 5.1 cf5d1142e9 release(v4.7.0): bump framework and eight skills to 4.7.0 in lockstep; archive PR #29 reviews
Issue #21 items 4, 5, 7 (worktree helpers, review identity, acceptance
A1-A8), #20 test isolation, #27 .gitignore managed block, #28 host
portability. VERSIONS.md carries the entry; the eight review reports of
PR #29 (two rounds plus the codex re-review) are archived under
.agents/reports/<session>/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-20 21:43:25 +09:00
GodopuandClaude Fable 5.1 80a5614f7c fix(issue-21): PR #29 delta review follow-up - prepare installs the delegate-job requirements, removes a half-made venv on install failure; prompt dirty flag sees untracked files
- worktree_prepare.sh installs the same files deploy/install.sh does
  (.agents/skills/multi-agent-mux-delegate-job/requirements.txt: paho-mqtt,
  PyYAML) plus requirements*.txt. Without paho the loop wrapper, stop_session
  and the subscriber tests fail in the worktree - seen as 4 suite failures
  in a worktree without its own venv, all passing with one.
- review D1: a failed pip install removes the venv it half made, so the next
  run recreates it instead of reporting VENV=kept (test w1c).
- review D3: the [worktree context] block's uncommitted-changes flag uses
  --untracked-files=all like the guards.
- review D2: the create skill doc says the install needs the package index.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-20 21:33:57 +09:00
GodopuandClaude Fable 5.1 08db1da9d5 fix(issue-21): PR #29 review R1-R3 - untracked-safe cleanliness checks; a worktree gets its own venv
R1/R2: `git status --porcelain` honours status.showUntrackedFiles, so a
`no` setting in a submodule (or the worktree) hid untracked files from the
checks that justify worktree_remove.sh's single --force and from the review
snapshot guard (_dirty_submodules), which then approved content it never
captured. All three checks (lib_py/worktree.py, worktree_remove.sh,
runner._dirty_submodules) now pass --untracked-files=all. The source tree's
own snapshot was already safe: `git add -A` ignores that setting.

R3: worktree_prepare.sh no longer links .venv to the control root's. It
creates the worktree's own venv with the control root's interpreter and
installs requirements*.txt; a leftover link is refused (exit 1), a control
root without an interpreter is a warning (VENV=skipped); VENV= is printed.

Tests: w1 (real venv, kept on re-run), w1b (link refused / skipped), w6e
parametrised over the hidden setting for the post-stop recheck, w6e2
(hidden untracked file in the submodule and in the worktree blocks the
removal), l17 untracked-hidden / during-hidden (guard refuses before the
first review; a mid-round hidden file makes the round stale). Each new
case fails without the fix.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-20 20:43:20 +09:00
GodopuandClaude Fable 5.1 90a289da44 test(issue-14): widen the wall-clock close margin under suite load
The close landed 8-9 s past the 20 s deadline in three full-suite runs on
this host (all passed alone); the pinned defect closed at ~2 x TIMEOUT,
so a 15 s margin keeps the test meaningful.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-20 19:49:37 +09:00
GodopuandClaude Fable 5.1 7a94ba2c3e test(issue-28): make the host-dependent tests portable; POSIX ps in the process scanners
The 33 failures on the Linux / Python 3.14 host (#28) were environment
dependencies, not regressions; each test is worth keeping, so fix the
dependency instead of deleting the test:

- test_workspace_scope: `source` under `shell=True` ran in dash; run the
  command string under bash.
- conftest autouse: MAM_ENV_FILE points at an absent file for every test,
  so a workspace .mam.env with a reachable broker no longer breaks the
  "broker unavailable" tests (T1-4).
- help-capture guard: compare normalized help text (whitespace collapsed,
  "optional arguments"/"options" unified) so argparse re-wrapping across
  Python versions is not drift.
- deploy freshness D-11..D-19 and D-22..D-30: skip with a reason when the
  nats-docker submodule is not initialized.
- lib_py.agents.base.conversation_in_use and heartbeat._process_table:
  `ps -eo pid=,ppid=,args=` (POSIX) instead of the BSD `-axo ... command=`
  form, the most likely cause of the 12 purge failures on Linux (the holder
  scan returned nothing); to be confirmed on that host.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-20 19:15:50 +09:00
GodopuandClaude Opus 5 46ea4cc1e4 test(issue-20): state isolation regression tests
S1/S2 per-test defaults and the mam_sandbox override, S3 submit with WORKSPACE_ROOT on a checkout leaves its .mam alone, S6 a nested pytest started with a pane's binding sees none of it (import-time copy included) and leaves the target untouched, S7 lib.sh takes the session registry from a test's WORKSPACE_ROOT.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sdga4A8i1iSc8DaaW9iKU2
2026-09-15 02:02:52 +00:00
GodopuandClaude Opus 5 a2be70df1b test(issue-20): issue-15 leak checks judge only their own job ids and session
The onboarding tests compared whole listings of the repo's .mam, so a concurrent run's entries failed them. Session names are now unique per run; the check is that the sandbox's own job ids are absent from every real root (this checkout's and the recorded control plane's) and that no new entry there names the session. cli_defaults also checks that the audit log follows WORKSPACE_ROOT.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sdga4A8i1iSc8DaaW9iKU2
2026-09-15 02:02:52 +00:00
GodopuandClaude Opus 5 c5844a8e12 test(issue-20): session guard for new entries in the recorded control planes' .mam/jobs and .mam/delegate_job_logs
Watches this checkout's .mam and the ones the recorded binding named. A new entry is a leak when it is an audit log without a registry record, or when its agent_session is missing or not a registered session; live delegations write the record first and target a registered session. Reports, never deletes. The #19 guard's docstring pointed at a #15 session guard that never existed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sdga4A8i1iSc8DaaW9iKU2
2026-09-15 02:02:52 +00:00