memon-notify

star 1

Manual + thin wrapper for `memon notify` — push a one-shot Telegram alert to the user when an autonomous run needs their attention. Use when the agent is stuck on a bug it cannot resolve, hit a fatal error, does not know why something died, needs a human judgment call it would otherwise raise via AskUserQuestion, or finished a long-running task the user delegated then walked away from. One notification per significant event — NEVER per loop iteration. Send-only: push and keep working, do not block waiting for a reply.

memset0 By memset0 schedule Updated 6/5/2026

name: memon-notify description: Manual + thin wrapper for memon notify — push a one-shot Telegram alert to the user when an autonomous run needs their attention. Use when the agent is stuck on a bug it cannot resolve, hit a fatal error, does not know why something died, needs a human judgment call it would otherwise raise via AskUserQuestion, or finished a long-running task the user delegated then walked away from. One notification per significant event — NEVER per loop iteration. Send-only: push and keep working, do not block waiting for a reply. argument-hint: "" [--details … | --details-file -] license: MIT metadata: author: memset0 version: "0.1.0"</h2> <h2>memon-notify</h2> <p>This skill is a manual for the <code>memon notify</code> CLI subcommand — how to push a single Telegram message to the user when an autonomous run needs their attention. The user has stepped away while a long task runs; a push notification lets them come back and handle whatever came up, dropping round-trip latency from hours to seconds.</p> <p><strong>Send-only.</strong> The bot pushes; it does not read replies. After notifying, keep working if you can — the notification is a signal to the human, not a blocking wait. For a decision you genuinely cannot proceed without <em>inside an interactive session</em>, still use AskUserQuestion; <code>memon notify question</code> is for "I parked this and pinged you", not "I am now blocked on the bot".</p> <p>There is no FS-convention preflight here: <code>memon notify</code> never touches the experiment tree (no run dirs, no exp docs, no journal). It only reads the <code>telegram:</code> block of <code>config.yml</code> and POSTs to Telegram.</p> <h2>When to use</h2> <p>Pick the severity that matches your situation:</p> <table> <thead> <tr> <th>severity</th> <th>when</th> </tr> </thead> <tbody><tr> <td>🔥 <code>error</code></td> <td>a fatal / unrecoverable failure — the run crashed and you can't recover, you don't know why it died, or a fix you tried did not work</td> </tr> <tr> <td>⚠️ <code>warn</code></td> <td>stuck-but-running — a bug you've been circling without progress, or a degraded-but-alive state worth a look</td> </tr> <tr> <td>❓ <code>question</code></td> <td>a human judgment call you would otherwise raise via AskUserQuestion (which baseline, which direction) — you've parked the work and want the user to weigh in</td> </tr> <tr> <td>✅ <code>done</code></td> <td>a long task the user delegated then walked away from has finished AND been verified</td> </tr> <tr> <td>ℹ️ <code>info</code></td> <td>a milestone worth surfacing that needs no action</td> </tr> </tbody></table> <h2>When NOT to use</h2> <ul> <li>❌ The user is actively in the conversation right now — just ask / tell them directly. A notification to someone who's already here is noise.</li> <li>❌ Per loop iteration / per step. One notification per <strong>significant</strong> event. A 50-step sweep is ONE <code>done</code>, not 50 <code>info</code>s.</li> <li>❌ As a durable log — that's <code>memon-append-journal</code> (a <code>[NOTE]</code> / <code>[ERROR]</code> event survives; a notification is an ephemeral nudge).</li> <li>❌ For a routine anomaly that belongs on the experiment doc — that's <code>memon-append-warning</code> (one OPEN row for human adjudication).</li> <li>❌ As a blocking wait. If you cannot proceed without an answer in an interactive session, use AskUserQuestion, not a notification.</li> </ul> <h2>Credentials</h2> <p><code>memon notify</code> reads the <code>telegram:</code> block from <code>config.yml</code> (or the <code>MEMON_TELEGRAM_BOT_TOKEN</code> + <code>MEMON_TELEGRAM_CHAT_ID</code> env vars). It does <strong>not</strong> take <code>--project-root</code> — pass <code>--config <path></code> when your cwd is not the directory holding <code>config.yml</code>, otherwise rely on the cwd <code>config.yml</code>.</p> <p>If the command exits <code>2</code> (BAD_REQUEST) naming missing credentials, surface this to the user (in Chinese):</p> <blockquote> <p>Telegram 通知还没配置好。请在 <code>config.yml</code> 里加一个 <code>telegram:</code> 块 (<code>bot_token</code> + <code>chat_id</code>,从 @BotFather 拿 token),或者设置 <code>MEMON_TELEGRAM_BOT_TOKEN</code> 和 <code>MEMON_TELEGRAM_CHAT_ID</code> 两个环境变量, 然后我再发一次。</p> </blockquote> <p>Do NOT echo the bot token anywhere — it is a credential. The CLI redacts it from its own error output; don't undo that by printing it.</p> <h2>Workflow</h2> <ol> <li><strong>Pick the severity</strong> from the When-to-use table.</li> <li><strong>Title</strong>: one line, ≤ 200 characters, names the specific situation. The title is what the user sees in the phone notification preview — make it scannable.</li> <li><strong>Body</strong> (optional): for anything multi-line — a stack trace, a repro, a short list of options — pipe markdown via <code>--details-file -</code> so you don't shell-escape every backtick / quote / dollar / newline. Markdown renders: <code>**bold**</code>, <code>*italic*</code>, <code>```fenced code```</code>, and <code>[links](url)</code>.</li> <li><strong>ALWAYS pass <code>--agent</code> and <code>--session</code></strong> so the footer says which agent and which conversation fired the ping. With several agents running on a cluster, this is how the user tells them apart.</li> <li>Add <code>--context key=value</code> for project / run ids and <code>--link <url></code> for a deep link into the dashboard.</li> <li><strong><code>--soft</code></strong> is opt-in, not default:<ul> <li>Use it when a lost notification must NOT break your loop (a transient Telegram outage → error to stderr, exit 0). Good for a mid-run <code>warn</code> fired from inside a retry loop.</li> <li>Omit it when delivery must be confirmed — e.g. the terminal <code>done</code> at the very end of a task, where you want a non-zero exit if the ping didn't land.</li> </ul> </li> </ol> <p>Example — a fatal error with a code-fenced body (outer fence is 4 backticks so the heredoc's own ``` fence doesn't close it):</p> <pre><code class="language-sh">cat <<'EOF' | memon notify error "training crashed: NCCL timeout" \ --details-file - \ --agent claude --session "$SESSION" \ --context project=sparse-fsdp --context run=tp4-260604 \ --link "https://memon.example/p/sparse-fsdp" \ --config ./config.yml **Stack trace** (rank 3): ``` torch.distributed.DistBackendError: NCCL all-reduce timed out @ step 1500 ``` Tried: rerun, lower TP degree. Still hangs. EOF </code></pre> <p>Output (JSON to stdout) on success:</p> <pre><code class="language-json">{ "sent": true, "severity": "error", "title": "training crashed: NCCL timeout", "agent": "claude", "session": "...", "telegram_chat_id": "...", "telegram_message_id": 7 } </code></pre> <p>A terminal <code>done</code> where you want delivery confirmed (no <code>--soft</code>):</p> <pre><code class="language-sh">memon notify done "100k-step finetune finished, eval acc 0.873" \ --agent claude --session "$SESSION" \ --context run=ft-260605 --config ./config.yml </code></pre> <h2>Anti-patterns</h2> <ul> <li>❌ Notifying on every iteration of a polling / retry loop. Fire once, on the significant transition.</li> <li>❌ Crying wolf with <code>error</code> for a non-fatal hiccup. Reserve 🔥 for "blocked / crashed", or the user learns to ignore it.</li> <li>❌ Pasting a 500-line log into <code>--title</code>. The title is one line; bulk goes in <code>--details</code> / <code>--details-file</code> (auto-truncated at 4096 UTF-16 units).</li> <li>❌ Omitting <code>--session</code> (or <code>--agent</code>). Without them the user can't tell which conversation pinged them.</li> <li>❌ Passing <code>--project-root</code> — <code>memon notify</code> rejects it. Use <code>--config <path></code> or a cwd <code>config.yml</code>.</li> <li>❌ Echoing the bot token. It lives in <code>config.yml</code>; the CLI redacts it from errors — don't print it yourself.</li> <li>❌ Treating <code>memon notify question</code> as a blocking call and waiting for a reply. It's send-only; keep working or use AskUserQuestion.</li> </ul> <h2>Errors</h2> <table> <thead> <tr> <th>exit</th> <th>meaning</th> </tr> </thead> <tbody><tr> <td>0</td> <td>sent (or <code>--soft</code> swallowed a send failure; the error is still on stderr)</td> </tr> <tr> <td>1</td> <td>telegram 4xx / 5xx, network error, or timeout (no <code>--soft</code>)</td> </tr> <tr> <td>2</td> <td><code>BAD_REQUEST</code> — unknown severity, empty / too-long title, a reserved <code>--context</code> key (<code>host</code>/<code>agent</code>/<code>session</code>/<code>cwd</code>/<code>branch</code>/<code>ts</code>), or no credentials anywhere</td> </tr> <tr> <td>4</td> <td><code>NOT_FOUND</code> — <code>--details-file <path></code> does not exist</td> </tr> </tbody></table> </article> </div> <!-- Right: Metadata & Command Sidebar --> <div class="w-full lg:w-80 shrink-0 flex flex-col gap-6" data-astro-cid-7zzsworf> <!-- Install Card --> <div class="p-6 rounded-xl bg-surface-container border border-border/80 flex flex-col gap-4 shadow-sm" data-astro-cid-7zzsworf> <span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant/60 font-mono" data-astro-cid-7zzsworf>Install via CLI</span> <div class="flex flex-col gap-2" data-astro-cid-7zzsworf> <div id="detail-install-cmd" class="font-mono text-[11px] p-3 rounded-lg bg-black/40 border border-border select-all break-all text-primary font-bold leading-relaxed" data-astro-cid-7zzsworf> npx skills add https://github.com/memset0/my-research-harness --skill memon-notify </div> <button id="detail-copy-btn" class="w-full py-2.5 rounded-lg bg-primary hover:bg-primary-hover text-on-primary font-sans font-bold text-sm shadow transition-all active:scale-95 flex items-center justify-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px]" data-astro-cid-7zzsworf>content_copy</span> <span data-astro-cid-7zzsworf>Copy Command</span> </button> </div> </div> <!-- Details & Stats Card --> <div class="p-6 rounded-xl bg-surface-container border border-border/80 flex flex-col gap-4 shadow-sm text-on-surface" data-astro-cid-7zzsworf> <span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant/60 font-sans" data-astro-cid-7zzsworf>Repository Details</span> <div class="flex flex-col gap-3.5" data-astro-cid-7zzsworf> <div class="flex justify-between items-center text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>star</span> Stars </span> <span class="font-mono font-bold text-on-surface" data-astro-cid-7zzsworf>1</span> </div> <div class="flex justify-between items-center text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>call_split</span> Forks </span> <span class="font-mono font-bold text-on-surface" data-astro-cid-7zzsworf>0</span> </div> <div class="flex justify-between items-center text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>navigation</span> Branch </span> <span class="font-mono bg-surface border border-border px-2 py-0.5 rounded text-[11px] text-on-surface-variant" data-astro-cid-7zzsworf>main</span> </div> <div class="flex justify-between items-start text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5 mt-0.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>article</span> Path </span> <span class="font-mono bg-surface border border-border px-2 py-0.5 rounded text-[11px] text-on-surface-variant truncate max-w-[150px]" title="SKILL.md" data-astro-cid-7zzsworf>SKILL.md</span> </div> </div> </div> <!-- Occupations Tag Card --> <!-- Related Creators Card --> <div class="p-6 rounded-xl bg-surface-container border border-border/80 flex flex-col gap-3 shadow-sm" data-astro-cid-7zzsworf> <span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant/60 font-sans" data-astro-cid-7zzsworf>More from Creator</span> <div class="flex items-center gap-2" data-astro-cid-7zzsworf> <img class="w-8 h-8 rounded-full border border-border" src="https://avatars.githubusercontent.com/u/34177126?u=2a2110a6543cea41af6a3920f6fd5f0fa11a9d8f&v=4" alt="memset0" onerror="this.src='https://avatars.githubusercontent.com/u/9919?v=4'" data-astro-cid-7zzsworf> <div class="flex flex-col min-w-0" data-astro-cid-7zzsworf> <span class="font-bold text-sm truncate text-on-surface" data-astro-cid-7zzsworf>memset0</span> <a href="/?creator=memset0" class="text-xs text-primary hover:underline font-semibold transition-all" data-astro-cid-7zzsworf>Explore all skills →</a> </div> </div> </div> </div> </div> </div> </div> <script> const copyBtn = document.getElementById("detail-copy-btn"); const installCmd = document.getElementById("detail-install-cmd"); if (copyBtn && installCmd) { copyBtn.addEventListener("click", () => { const cmd = installCmd.textContent.trim(); navigator.clipboard.writeText(cmd).then(() => { const originalText = copyBtn.innerHTML; copyBtn.innerHTML = ` <span class="material-symbols-outlined text-[16px]">check</span> <span>Copied!</span> `; copyBtn.style.background = "#10b981"; copyBtn.style.borderColor = "#10b981"; setTimeout(() => { copyBtn.innerHTML = originalText; copyBtn.style.background = ""; copyBtn.style.borderColor = ""; }, 1500); }); }); } </script> </div> <!-- Footer --> <footer class="border-t border-border bg-surface-container-low text-on-surface-variant py-8 px-gutter mt-16 rounded-xl"> <div class="max-w-container-max mx-auto flex flex-col md:flex-row justify-between items-center gap-6"> <div class="flex items-center gap-2"> <div class="w-6 h-6 rounded bg-primary bg-opacity-20 flex items-center justify-center"> <span class="material-symbols-outlined text-primary text-sm">code_blocks</span> </div> <span class="font-bold text-on-surface text-sm">SkillMD</span> </div> <div class="flex flex-wrap justify-center gap-6 text-sm"> <a href="/about" class="hover:text-primary transition-colors">About Us</a> <a href="/contact" class="hover:text-primary transition-colors">Contact Us</a> <a href="/privacy" class="hover:text-primary transition-colors">Privacy Policy</a> <a href="/terms" class="hover:text-primary transition-colors">Terms of Service</a> <a href="/support" class="hover:text-primary transition-colors">Support</a> </div> <div class="text-xs text-on-surface-variant/80"> © 2026 SkillMD. All rights reserved. </div> </div> </footer> </main> <!-- Script for Theme Toggle, Mobile Menu, and Sidebar Filter Redirection --> <script> // Theme setup const savedTheme = localStorage.getItem("theme") || "dark"; function applyTheme(theme) { document.documentElement.classList.remove("dark", "green", "dracula", "nord"); if (theme === "dark") { document.documentElement.classList.add("dark"); } else if (theme === "green") { document.documentElement.classList.add("dark", "green"); } else if (theme === "dracula") { document.documentElement.classList.add("dark", "dracula"); } else if (theme === "nord") { document.documentElement.classList.add("dark", "nord"); } document.documentElement.setAttribute("data-theme", theme); const themeMoon = document.getElementById("theme-moon"); const themeSun = document.getElementById("theme-sun"); const themeLeaf = document.getElementById("theme-leaf"); const themeDracula = document.getElementById("theme-dracula"); const themeNord = document.getElementById("theme-nord"); if (themeMoon && themeSun && themeLeaf && themeDracula && themeNord) { themeMoon.style.display = theme === "dark" ? "inline" : "none"; themeSun.style.display = theme === "light" ? "inline" : "none"; themeLeaf.style.display = theme === "green" ? "inline" : "none"; themeDracula.style.display = theme === "dracula" ? "inline" : "none"; themeNord.style.display = theme === "nord" ? "inline" : "none"; } } applyTheme(savedTheme); const themeToggleBtn = document.getElementById("theme-toggle-btn"); if (themeToggleBtn) { themeToggleBtn.addEventListener("click", () => { const currentTheme = document.documentElement.getAttribute("data-theme") || "dark"; let newTheme = "dark"; if (currentTheme === "dark") { newTheme = "light"; } else if (currentTheme === "light") { newTheme = "green"; } else if (currentTheme === "green") { newTheme = "dracula"; } else if (currentTheme === "dracula") { newTheme = "nord"; } else { newTheme = "dark"; } applyTheme(newTheme); localStorage.setItem("theme", newTheme); }); } // Mobile menu toggle and sidebar logic const mobileMenuToggle = document.getElementById("mobile-menu-toggle"); const sidebarMenu = document.getElementById("sidebar-menu"); const sidebarOverlay = document.getElementById("sidebar-overlay"); function isMobile() { return window.innerWidth < 768; // 768px is the 'md' breakpoint in Tailwind } function openSidebar() { if (sidebarMenu) { sidebarMenu.classList.remove("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.remove("hidden"); } } function closeSidebar() { if (sidebarMenu && isMobile()) { sidebarMenu.classList.add("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.add("hidden"); } } if (mobileMenuToggle && sidebarMenu) { mobileMenuToggle.addEventListener("click", (e) => { e.stopPropagation(); if (isMobile()) { const isClosed = sidebarMenu.classList.contains("-translate-x-full"); if (isClosed) { openSidebar(); } else { closeSidebar(); } } }); document.addEventListener("click", (e) => { if (isMobile()) { if (!sidebarMenu.contains(e.target) && !mobileMenuToggle.contains(e.target)) { closeSidebar(); } } }); if (sidebarOverlay) { sidebarOverlay.addEventListener("click", () => { if (isMobile()) { closeSidebar(); } }); } // Collapse sidebar when clicking a filter button, creator button, or nav item inside it sidebarMenu.addEventListener("click", (e) => { if (isMobile()) { const clickTarget = e.target.closest("button, a"); if (clickTarget) { closeSidebar(); } } }); // Sync sidebar state on window resize window.addEventListener("resize", () => { if (!isMobile()) { // Desktop: sidebar should be visible, no overlay if (sidebarMenu) { sidebarMenu.classList.remove("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.add("hidden"); } } else { // Mobile: start collapsed if (sidebarMenu) { sidebarMenu.classList.add("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.add("hidden"); } } }); } // If not on homepage, redirect on sidebar filter click const isHomepage = window.location.pathname === "/"; document.querySelectorAll("#occupation-filters .filter-btn").forEach(btn => { btn.addEventListener("click", (e) => { const occ = e.currentTarget.getAttribute("data-occupation"); if (!isHomepage) { window.location.href = occ ? `/?occupation=${encodeURIComponent(occ)}` : "/"; } }); }); document.querySelectorAll("#creator-filters .creator-btn").forEach(btn => { btn.addEventListener("click", (e) => { const creator = e.currentTarget.getAttribute("data-creator"); if (!isHomepage) { window.location.href = `/?creator=${encodeURIComponent(creator)}`; } }); }); </script> </body> </html>