From be7c4f82e8e3fa121f757a68c698d327fb5b0af9 Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Thu, 16 Apr 2026 07:16:14 +0000 Subject: [PATCH] feat(run): startup + shutdown ping on both channels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Answers 'is it even running?' within seconds of 'atm run' — no waiting 30 min for heartbeat + no need for a live trigger to occur. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/atm/main.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/atm/main.py b/src/atm/main.py index 17d8c48..efeab2e 100644 --- a/src/atm/main.py +++ b/src/atm/main.py @@ -282,6 +282,16 @@ def run_live(cfg, duration_s=None, capture_stub: bool = False) -> None: ] notifier = FanoutNotifier(backends, Path(cfg.dead_letter_path)) + # Startup ping — confirms both channels work before the first bar closes. + from datetime import datetime + dur_note = f" duration={cfg.heartbeat_min:d}m heartbeat" if duration_s is None else f" duration={duration_s/3600:.2f}h" + notifier.send(Alert( + kind="heartbeat", + title="ATM started", + body=f"config={cfg.config_version}{dur_note} at {datetime.now().isoformat(timespec='seconds')}", + )) + audit.log({"event": "started", "config": cfg.config_version}) + start = time.monotonic() heartbeat_due = start + cfg.heartbeat_min * 60 levels_extractor = None @@ -337,6 +347,13 @@ def run_live(cfg, duration_s=None, capture_stub: bool = False) -> None: heartbeat_due = time.time() + cfg.heartbeat_min * 60 time.sleep(cfg.loop_interval_s) finally: + try: + notifier.send(Alert( + kind="heartbeat", title="ATM stopped", + body=f"after {time.monotonic() - start:.0f}s", + )) + except Exception: + pass notifier.stop() audit.close()