Review round 5 (planner-reviewer-codex-01, NOT PASS at 2b1d590):
- F1 bounded lock cycle: the stopper holds the per-job lifecycle lock while
it waits for the timer to exit, and the timer's exit cleanup waited for
that same lock, so every stop ran out the drain and ended in SIGKILL with
a false "did not drain" warning. The exiting timer now tries the lock
without blocking (lifecycle_lock(blocking=False)); when it is busy the
ownership operation holding it removes the pidfile, and when it is free
the own-pid comparison is kept.
- F2 untracked owner: when writing the new timer's pid fails after the
spawn, _spawn_timer_locked kills and reaps that exact process while the
lock is still held and drops only a partial pidfile of that attempt,
then re-raises.
- tests: an idle timer stops well before a 10 s drain without SIGKILL;
a pidfile failure after spawn leaves no survivor, no pidfile and a free
lock (both fail on 2b1d590); a spawn failure leaves nothing behind and
the next start works.
- docs: MESSAGING 2.5 and VERSIONS item 1 describe the lifecycle lock;
VERSIONS test count 31.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
Review round 3 (planner-reviewer-codex-01, NOT PASS R3 at 7e1d3f0):
start_timer stopped a running timer with a bare TERM and overwrote the only
pidfile. A beat the old timer had already admitted - seq taken, recorded on
disk, held at the wire - stayed alive but undiscoverable, so a later
terminal publish drained only the replacement and the old beat could still
put a retained `running` on the broker after `completed`.
- heartbeat.start_timer: stop_timer(drain=MAM_HEARTBEAT_DRAIN_S). The old
timer is drained or killed, and confirmed gone, before the pidfile names
the new one. A terminal publish racing the replacement either drains the
same old pid or meets a new timer whose beats the barrier refuses.
- test: an old timer held at the wire after admission is replaced, then
the job completes; the old timer is gone before the handover and nothing
it held follows the terminal (fails on 7e1d3f0).
- docs: MESSAGING 4.3 no longer says any state resets the idle clock;
MESSAGING 2.5 and VERSIONS state the replacement rule; test count 26.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
The subscriber is started before the injection, and the injection itself
(idle handshake, quiescence, delivery grace) can take tens of seconds, so
a first-event budget of T + K x N measured from the subscription could
expire before `started` was even possible. The wrapper now passes
--ack-timeout <wall clock>: it judges delivery with _wait_started (T from
the injection) and reaps the subscriber on failure; K x N applies from the
first event on. A standalone subscriber keeps T + K x N. Documented in
MESSAGING.md 4.3.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
Review round 2 (planner-reviewer-codex-01, NOT PASS at d09c162):
- R1 remote.mirror_job: admission under the registry lock. The same
dispatch_token is idempotent (a finished attempt never runs again), a
lower attempt or the same attempt under another token is refused before
any write, and only a strictly newer attempt replaces the record
(register_job(supersede_older_attempt=True); events.log kept, seqs stay
monotonic).
- R2 remote.handle_dispatch: the --exec command runs in its own session;
at timeout_sec, or when the runner is interrupted (SIGTERM now unwinds
it), the whole process group is terminated (TERM, then KILL) before the
error is published.
- R3 terminal barrier: a terminal publish raises an attempt-scoped barrier
under the lock that allocates seqs, stops the heartbeat timer, gives it
MAM_HEARTBEAT_DRAIN_S (default 1 s) to exit and then kills it, and only
then takes its own seq. Beats are admitted under that lock
(next_seq(admit=...)); a beat still on the wire is cancelled with the
timer rather than holding the terminal back; and a late non-terminal
event never publishes a running state over a terminal record.
- R4 job_subscriber: a retained state renews the idle clock only when its
seq is newer than everything already accepted; re-delivered or older
snapshots still recover a terminal status.
- tests: five regressions, all failing on 77f2829; T1-14's wall clock
15 -> 30 s against public-broker latency.
- docs: MESSAGING 2.5 (barrier, state renewal, detection vs final close),
heartbeat docstring, loop SKILL.md (remote rows need the Python runner),
VERSIONS v4.5.0 items 1/4/5 and the test count (25).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
In the v4.5.0 review round grok read the brief and published `started`
70 s after the injection; with T = 60 the wrapper had already closed the
job as agent_blocked and refused its later PASS. T bounds delivery, not
work, so a generous default costs nothing when the agent answers fast.
Code, docs and the CLI snapshot updated together.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
Review round 1 (claude NOT PASS) corrections:
- B2: the timer now watches a process - `--watch-pid`, or by default the
nearest agent-CLI ancestor of the publishing command - and stops within
one period once it is gone, so a crashed agent stops heartbeating and is
closed after K x N; a remote runner watches itself. Documented as process
liveness (an alive-but-stuck agent keeps heartbeating).
- R1: `_wait_started` accepts an events.log `started` only when it carries
the current dispatch token (loop jobs share one events.log).
- R2: the remote runner ignores a re-sent dispatch of an attempt that
already reached its terminal.
- R3: registry.md EOF blank line (git diff --check clean).
- R4: the ack error states the effective budget when the wall clock cut it.
- P3: stop_timer verifies the pidfile pid is this job's timer before
signalling; a retained state message keeps its own seq watermark so it
can never make the monotonic guard drop its own event.
- MULTI_AGENT_RULES.md timeout section updated for the contract.
- tests: agent-process death stops the timer, recycled-pid safety,
attempt-scoped started, re-sent dispatch; acceptance 3 now kills the
agent process instead of the timer. Help captures refreshed
(--watch-pid). The wrapper can be sourced with
MAM_DELEGATE_JOB_NO_MAIN=1 for helper-level tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
Contract (no new event types): heartbeat every N s (automatic timer),
unresponsive after K x N s without an event, `started` within T s of the
injection, retained per-job state, and a remote transport.
- registry: heartbeat_sec / heartbeat_misses / ack_timeout_sec / transport
fields; idle_timeout_sec derives K x N unless explicit (defaults 30/4/60
keep the historical 120 s idle window).
- heartbeat.py + publish_event --heartbeat N: detached fixed-cadence timer
publishing signed `progress` heartbeats (threaded wire publish, local
record first); stopped by the terminal event via pidfile, by re-dispatch
and by lifetime. Terminal events refuse --heartbeat.
- publish_event: retained `<prefix>/state` (status, last_event, seq, HMAC)
sent on the same connection after every event (--no-state to skip);
publisher.publish_raw takes extra messages.
- job_subscriber: subscribes the state topic, recovers terminal status from
retained state, resets idle on any accepted message, records seq_gap and
reconnected in the audit log, idle budget before the first event is
T + K x N (--ack-timeout), --remote mirrors started/terminal events into
the registry through the transition boundary.
- lib.sh send_keys_safe: an accepted-but-unconfirmed submission is confirmed
by agent_status working or the job's own `started` (_sks_job_started);
pane change and busy token are no longer delivery signals.
- wrapper: --heartbeat/--heartbeat-misses/--ack-timeout (MAM_* env),
injected start command arms the timer, brief states the contract,
_wait_started (registry / events.log / subscriber log, bounded by T and
the wall clock) replaces the 10 s inject grace, R1 grace uses only the
on-disk event age (herdr/pane logged once as a diagnostic),
remote:<name> sessions skip preflight/idle/pane and dispatch over the
broker (remote_dispatch).
- remote.py (+ scripts shim): signed retained dispatch (MAM_DISPATCH_SECRET)
and a runner that mirrors the job into its own registry with the
orchestrator's identity, publishes started --heartbeat, runs --exec and
publishes the terminal event.
- loop runner: passes MAM_HEARTBEAT_SEC / MAM_HEARTBEAT_MISSES /
MAM_ACK_TIMEOUT_S through; YAML rows with transport: remote delegate as
remote:<name>.
- docs: MESSAGING.md 2.5 contract + remote transport, delegate-job SKILL.md,
registry.md, loop SKILL.md.
- tests: tests/test_issue18_liveness_contract.py (16); issue 14 pane-signal
tests flipped to started-based; issue 6 working-agent tests acknowledge
with started; CLI snapshot/help captures refreshed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
PATCH release for the fix/issue-12-8-7 branch (Issue #12 broker password
kept out of job records, #8 fresh install stamps kept on update, #7
same-cwd workspaces packed). No code logic changes.
- lib.sh MAM_VERSION, lib_py/loop/eager.py FRAMEWORK_VERSION and the eight
SKILL.md frontmatters (create, stop, resume, status, monitor, delegate-job,
loop, orc-onboard) move from 4.4.3 to 4.4.4
- VERSIONS.md: current release v4.4.4 (2026-09-13 KST), the eight-row skill
matrix, and a v4.4.4 changelog section in the v4.4.3 format covering the
password-free registry block restored from MQTT_PASSWORD at connect time,
the scrub on every record write, `registry.py scrub-secrets [--dry-run]`,
why auth_token/dispatch_token stay, the update.sh stamp and install_state
merge rules, the sibling-workspace walk in new-session, and the 8 new tests
- VERSIONS.md v4.4.3 ledger correction: its new-test count predated fae70c1,
which added two purge tests; now 26 (19 + 7) with both listed
- README, README.ko, BOOTSTRAP and BOOTSTRAP.ko badges and the MESSAGING.md
"Applies to" header follow the current version; historical 4.4.3 mentions
(v4.4.3 changelog, reviewer reports) stay as history
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
PATCH release for the fix/issue-10-11-13-15 branch (Issue #10 agy purge
sidecars, #11 orc_onboard message, #13 orchestrator conversation purge,
#15 workspace-anchored job registry). No code logic changes.
- lib.sh MAM_VERSION, lib_py/loop/eager.py FRAMEWORK_VERSION and the eight
SKILL.md frontmatters (create, stop, resume, status, monitor, delegate-job,
loop, orc-onboard) move from 4.4.2 to 4.4.3
- VERSIONS.md: current release v4.4.3 (2026-09-13 KST), the eight-row skill
matrix, and a v4.4.3 changelog section in the v4.4.2 format covering the
agy purge sidecars/annotation/presence lock and residual_artifacts,
`lib_py.agents purge` and `orc_onboard.sh --remove --purge-conversation`
with their exit codes, the past-tense message, the MAM_JOBS_DIR ->
WORKSPACE_ROOT -> cwd registry/log resolution and the 20 new tests
- README, README.ko, BOOTSTRAP and BOOTSTRAP.ko badges and the MESSAGING.md
"Applies to" header follow the current version; historical 4.4.2 mentions
(v4.4.2 changelog, reviewer reports) stay as history
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
PATCH release for the fix/issue-9-14 branch (Issue #9 herdr server env
isolation, Issue #14 accepted-prompt delivery confirmation). No code logic
changes.
- lib.sh MAM_VERSION, lib_py/loop/eager.py FRAMEWORK_VERSION and the eight
SKILL.md frontmatters (create, stop, resume, status, monitor, delegate-job,
loop, orc-onboard) move from 4.4.1 to 4.4.2
- VERSIONS.md: current release v4.4.2 (2026-09-12 KST), the eight-row skill
matrix, and a v4.4.2 changelog section in the v4.4.1 format covering the
server env marker denylist, MAM_NO_AUTOSTART, the shim ls "no server
running" translation, SKS_DELIVERY_GRACE_S, MAM_INJECT_ACK_GRACE_S, the R1
pane busy-token grace, the T1-7 trigger change and the 13 new tests
- README, README.ko, BOOTSTRAP and BOOTSTRAP.ko badges and the MESSAGING.md
"Applies to" header follow the current version; historical 4.4.1 mentions
(v4.4.1 changelog, IMPROVEMENTS.md ledger, docs/contracts/issue-6, reviewer
reports) stay as history
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
- Relocate legacy implementation_plan.md to docs/contracts/nats-backplane/implementation_plan.md
- Synchronize root documentation (README, README.ko, BOOTSTRAP, BOOTSTRAP.ko, AGENTS, MESSAGING, IMPROVEMENTS) to latest v4.4.0 standards
- Consolidate IMPROVEMENTS.md to single source of truth (5 open / 31 completed tasks 1:1 matching section headers)
- Archive unanimous PASS review reports from Claude, Grok, Codex, and OpenCode along with Plan Rev.2