fix(#23, #24): 교차 서버 herdr 스코프와 에이전트 무관 시작 다이얼로그, 실제 에이전트가 충족하는 온보딩 전달 판정 #35

Merged
Godopu merged 15 commits from fix/issue-23-24 into main 2026-09-23 23:36:37 +00:00
3 changed files with 43 additions and 12 deletions
Showing only changes of commit 33b91fe0c8 - Show all commits

No files matched your search

@@ -331,8 +331,12 @@ cleanup_herdr_on_error() {
# Release the onboarding job: `cancelled` is a terminal transition the
# registry knows, and it says what happened - the job was never really
# dispatched. Best effort; a failure here must not stop the rollback.
if [ -n "${DELEGATE_JOB_ID:-}" ]; then
# `completed`/`error`/`cancelled` are terminal and published exactly once, so
# a job this run already closed is left alone - a second one is refused by
# the registry anyway ("conflicting cancelled after terminal error").
if [ -n "${DELEGATE_JOB_ID:-}" ] && [ "${JOB_TERMINALIZED:-0}" != "1" ]; then
delegate_publish_event "$DELEGATE_JOB_ID" cancelled "rolled back by create_session" "${DELEGATE_JOB_TOKEN:-}" "${DELEGATE_JOB_ATTEMPT:-}" || true
JOB_TERMINALIZED=1
fi
if [ "$alive" = "1" ]; then
@@ -645,6 +649,7 @@ Task: $SUBMIT_JOB_PROMPT"
# text never appeared - none of which a later `started` can repair.
if [ "$rc" -ne 0 ] && [ "$rc" -ne 4 ]; then
delegate_publish_event "$DELEGATE_JOB_ID" error "instruction injection failed (prompt-lock, rc=$rc)" "$DELEGATE_JOB_TOKEN" "$DELEGATE_JOB_ATTEMPT"
JOB_TERMINALIZED=1
exit 1
fi
if [ "$rc" -eq 4 ]; then
@@ -654,10 +659,26 @@ Task: $SUBMIT_JOB_PROMPT"
# ISSUE-23: this script no longer publishes `started` on the agent's behalf.
# It used to, which made the registry show a started job even when the agent
# never saw the prompt, and left any started-based verdict meaningless.
if ! delegate_wait_started "$DELEGATE_JOB_ID" "${DELEGATE_JOB_ACK_S:-180}" "$DELEGATE_JOB_TOKEN"; then
delegate_publish_event "$DELEGATE_JOB_ID" error "onboarding delivery unconfirmed: no started within ${DELEGATE_JOB_ACK_S:-180}s" "$DELEGATE_JOB_TOKEN" "$DELEGATE_JOB_ATTEMPT"
echo "ERROR: '$SESSION_NAME' did not acknowledge the onboarding prompt within ${DELEGATE_JOB_ACK_S:-180}s" >&2
exit 1
#
# How long to wait for the handshake: MAM_ONBOARD_ACK_S, else the wrapper's
# MAM_INJECT_ACK_GRACE_S, else the job's own ack timeout. The create-specific
# variable comes first so this wait can be shortened - in a sandbox with no
# real agent, for instance - without also retuning the delegate-job
# wrapper's injection ack, which is a different wait with its own tests.
ONBOARD_ACK_S="${MAM_ONBOARD_ACK_S:-${MAM_INJECT_ACK_GRACE_S:-${DELEGATE_JOB_ACK_S:-180}}}"
if ! delegate_wait_started "$DELEGATE_JOB_ID" "$ONBOARD_ACK_S" "$DELEGATE_JOB_TOKEN"; then
# The job failed, the session did not: the pane is up and the row is valid,
# only the handshake never landed. Killing a live agent over a slow or
# silent onboarding would destroy more than it protects, and it would
# contradict this script's own rule for the same situation one step
# earlier - a TUI that never signalled ready is preserved for diagnosis,
# not rolled back. So: mark the job, warn loudly, keep the session.
# MULTI_AGENT_RULES §6 has the GM gate on this job's `completed` anyway,
# so an unacknowledged onboarding already blocks real work.
delegate_publish_event "$DELEGATE_JOB_ID" error "onboarding delivery unconfirmed: no started within ${ONBOARD_ACK_S}s" "$DELEGATE_JOB_TOKEN" "$DELEGATE_JOB_ATTEMPT"
JOB_TERMINALIZED=1
echo "WARNING: '$SESSION_NAME' did not acknowledge the onboarding prompt within ${ONBOARD_ACK_S}s." >&2
echo " The session is preserved and job $DELEGATE_JOB_ID is marked error; re-send the onboarding with multi-agent-mux-delegate-job submit once the agent is up." >&2
fi
# Trigger immediate priority reconcile cycle asynchronously
# Issue #21 phase 2 (A8): the control root's reconcile/watchdog, never the
+6
View File
@@ -227,6 +227,12 @@ def mam_sandbox(tmp_path, monkeypatch, request):
# (`reconcile.sh --subscribe --idle-timeout 0`) from a sandbox. Tests that
# exercise the daemon itself override this with "0" or delenv.
monkeypatch.setenv("MAM_NO_SUBSCRIBER_DAEMON", "1")
# Issue #23: onboarding delivery now waits for the agent's own `started`.
# No sandbox has a real agent to publish one, so without a short grace
# every create_session test would sit out the full 180 s ack timeout.
# Create-specific on purpose: MAM_INJECT_ACK_GRACE_S is the delegate-job
# wrapper's knob and its own tests assert on the value they pass.
monkeypatch.setenv("MAM_ONBOARD_ACK_S", "3")
monkeypatch.delenv("HERDR_SESSION_NAME", raising=False)
monkeypatch.delenv("HERDR_SERVER_NAME", raising=False)
monkeypatch.delenv("HERDR_WORKSPACE_ID", raising=False)
+11 -7
View File
@@ -539,19 +539,23 @@ def test_s7c_delivery_succeeds_when_the_registry_left_pending(mam_sandbox):
assert "DELIVERED" in res.stdout, res.stdout + res.stderr
def test_s7d_no_started_within_t_is_a_delivery_failure(mam_sandbox):
"""S7d: fail closed, and say so on the job."""
def test_s7d_no_started_marks_the_job_but_keeps_the_session(mam_sandbox):
"""S7d: the job failed, the session did not. The handshake is marked
`error` and warned about loudly, but a live agent is not killed over a
slow onboarding - the same rule this script already applies one step
earlier to a TUI that never signalled ready."""
res, published = _run_delivery(mam_sandbox, 0)
assert res.returncode == 1, res.stdout + res.stderr
assert "DELIVERED" not in res.stdout
assert res.returncode == 0, res.stdout + res.stderr
assert published == ["error"], published
assert "did not acknowledge the onboarding prompt" in res.stderr
assert "preserved" in res.stderr
def test_s7e_a_started_from_another_dispatch_is_not_evidence(mam_sandbox):
"""S7e: a loop job keeps one events.log across attempts, so only a started
carrying THIS dispatch token counts."""
res, _ = _run_delivery(mam_sandbox, 0, started_token="tok-OTHER")
assert res.returncode == 1, res.stdout + res.stderr
res, published = _run_delivery(mam_sandbox, 0, started_token="tok-OTHER")
assert published == ["error"], published
def test_s8_rc4_is_a_warning_and_the_started_wait_decides(mam_sandbox):
@@ -564,8 +568,8 @@ def test_s8_rc4_is_a_warning_and_the_started_wait_decides(mam_sandbox):
assert published == [], published
res, published = _run_delivery(mam_sandbox, 4)
assert res.returncode == 1, res.stdout + res.stderr
assert published == ["error"], published
assert "did not acknowledge the onboarding prompt" in res.stderr
@pytest.mark.parametrize("rc", [1, 2, 3])