test(issue-18): acknowledge with started in the remaining working-agent scenarios; refresh the subscriber help capture
T1-14 and T1-20 model an agent that goes `working` and completes late. Under
the liveness contract an agent that never publishes `started` is a delivery
failure, so the watcher threads now acknowledge (non-blocking: the record
lands in events.log before the wire publish) and the late completion is
honoured inside the K x N grace as before. help-job_subscriber.txt records
the intended --ack-timeout option.
Full suite at 66f23c7: 888 passed, 3 failed (these), 0 leaked daemons.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
This commit is contained in:
1 parent
66f23c7cbd
commit
c17c87439d
2 files changed
+36
-24
No files matched your search
+6
-1
@@ -1,5 +1,6 @@
|
||||
usage: job_subscriber.py [-h] (--job JOB | --wait-any) [--timeout TIMEOUT]
|
||||
[--idle-timeout IDLE_TIMEOUT] [--expect-retention]
|
||||
[--idle-timeout IDLE_TIMEOUT]
|
||||
[--ack-timeout ACK_TIMEOUT] [--expect-retention]
|
||||
[--expect-dispatch-token EXPECT_DISPATCH_TOKEN]
|
||||
[--attempt-id ATTEMPT_ID]
|
||||
[--registry-dir REGISTRY_DIR] [--remote] [-v]
|
||||
@@ -15,6 +16,10 @@ optional arguments:
|
||||
--idle-timeout IDLE_TIMEOUT
|
||||
max seconds with no new event (default:
|
||||
job.idle_timeout_sec or 120)
|
||||
--ack-timeout ACK_TIMEOUT
|
||||
extra idle budget before the first event, for the
|
||||
injection to produce `started` (default:
|
||||
job.ack_timeout_sec; Issue #18)
|
||||
--expect-retention warn if no retained terminal event arrives promptly
|
||||
--expect-dispatch-token EXPECT_DISPATCH_TOKEN
|
||||
expected dispatch token for CAS verification
|
||||
|
||||
@@ -522,6 +522,34 @@ def test_t3_3_runner_delegate_job_session_busy_retry(tmp_path, monkeypatch):
|
||||
assert jid == "jid_busy_2"
|
||||
|
||||
|
||||
def _publish_started_for_registered_job(reg_dir, cwd, env=None):
|
||||
"""Issue #18: a working agent that never publishes `started` is a delivery
|
||||
failure (rc 1) under the liveness contract. The R1 re-observation cases
|
||||
below are about an agent that *did* start and then went silent, so the
|
||||
watcher threads acknowledge the job the way the injected command does."""
|
||||
deadline = time.time() + 20
|
||||
while time.time() < deadline:
|
||||
files = list(Path(reg_dir).glob("*.json"))
|
||||
if files:
|
||||
jid = files[0].stem
|
||||
tok = json.loads(files[0].read_text()).get("dispatch_token") or ""
|
||||
args = [sys.executable, str(SKILLS / "multi-agent-mux-delegate-job" / "scripts" / "publish_event.py"),
|
||||
"--registry-dir", str(reg_dir), "--job", jid, "--event", "started", "--attempts", "1"]
|
||||
if tok:
|
||||
args += ["--expect-dispatch-token", tok]
|
||||
run_env = dict(os.environ)
|
||||
run_env.update(env or {})
|
||||
# Fire and forget: the record lands in events.log before the wire
|
||||
# publish, and blocking here on the broker ACK (seconds) would
|
||||
# delay the scenario steps that follow in the watcher thread.
|
||||
subprocess.Popen(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
cwd=str(cwd), env=run_env)
|
||||
return jid
|
||||
time.sleep(0.1)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# T1-14: R1 working 중 subscriber 타임아웃 -> 디스크 completed 재관측 수용 (rc=0)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -547,6 +575,7 @@ def test_t1_14_working_agent_reobserve_completed(tmp_path, mock_herdr):
|
||||
if any("agent" in c and "prompt" in c for c in st.get("calls", [])):
|
||||
st["agents"]["agt_w1"]["agent_status"] = "working"
|
||||
state_file.write_text(json.dumps(st, indent=2))
|
||||
_publish_started_for_registered_job(reg_dir, tmp_path) # Issue #18: acknowledge
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
@@ -603,29 +632,6 @@ def test_t1_14_working_agent_reobserve_completed(tmp_path, mock_herdr):
|
||||
assert len(agent_gets) >= 1, "R1 loop must query agent get"
|
||||
|
||||
|
||||
def _publish_started_for_registered_job(reg_dir, cwd, env=None):
|
||||
"""Issue #18: a working agent that never publishes `started` is a delivery
|
||||
failure (rc 1) under the liveness contract. The R1 re-observation cases
|
||||
below are about an agent that *did* start and then went silent, so the
|
||||
watcher threads acknowledge the job the way the injected command does."""
|
||||
deadline = time.time() + 20
|
||||
while time.time() < deadline:
|
||||
files = list(Path(reg_dir).glob("*.json"))
|
||||
if files:
|
||||
jid = files[0].stem
|
||||
tok = json.loads(files[0].read_text()).get("dispatch_token") or ""
|
||||
args = [sys.executable, str(SKILLS / "multi-agent-mux-delegate-job" / "scripts" / "publish_event.py"),
|
||||
"--registry-dir", str(reg_dir), "--job", jid, "--event", "started", "--attempts", "1"]
|
||||
if tok:
|
||||
args += ["--expect-dispatch-token", tok]
|
||||
run_env = dict(os.environ)
|
||||
run_env.update(env or {})
|
||||
subprocess.run(args, capture_output=True, text=True, cwd=str(cwd), env=run_env, timeout=60)
|
||||
return jid
|
||||
time.sleep(0.1)
|
||||
return None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# T1-15: R1 working 고정 + 미발행 -> Issue #18: K×N 후 rc=2 error (working 은 진단일 뿐)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -886,6 +892,7 @@ def test_t1_20_loop_worker_late_completed_continues_to_reviewer(tmp_path, mock_h
|
||||
if any("agent" in c and "prompt" in c for c in st.get("calls", [])):
|
||||
st["agents"]["agt_worker"]["agent_status"] = "working"
|
||||
state_file.write_text(json.dumps(st, indent=2))
|
||||
_publish_started_for_registered_job(reg_dir, tmp_path) # Issue #18: acknowledge
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
Reference in new issue
Block a user