.env to write, no Docker to maintain. Open the Space, the swarm runs. August 9 2026 — refresh of the Aug-8 director cut; same 8 tabs, same 0-config shape, plus a 24h delta callout where the numbers moved.| Layer | Pick | License |
|---|---|---|
| Runtime | Gradio 5.13 Jul 2026 — Blocks + async queue, no Streamlit | Apache-2.0 |
| Host | HuggingFace Spaces — sdk: gradio, zero Docker, hardware L4 | SaaS |
| Worker SDK | huggingface_hub + requests (no grpcio, no extras) | Apache-2.0 |
| Search | web_search + web_fetch (zero-config browser tool) | MIT |
| Model registry | All AMT models published to kaidjuric/mtm-… on HF Hub | mixed |
| Scheduler | gradio.utils.set_timeout + APScheduler, no external cron | MIT |
| Persistence | HF Datasets (Parquet on Hub) for sweep.json + cycle history | Apache-2.0 |
| Verification | Puppeteer headless check, screenshots baked into STATUS.json | Apache-2.0 |
| Audio I/O | ffmpeg static binary in packages.txt (HF Spaces native) | LGPL |
| Score render | verovio pip wheel + music21 post-processor | MIT |
| Logs | gradio logger + HF Space "Logs" tab (live tail) | — |
app.py, one requirements.txt, three cadence hooks, one cron loop inside Gradio. git push → Space rebuilds → first sweep runs inside 90 seconds. The L4 hardware tier covers every model in the stack without overshoot. No env vars, no secrets manager, no Docker layer — HF Spaces gives you a sandboxed Linux with ffmpeg preinstalled and a free CPU tier that's enough to run a weekly sweep cold. The 30-day plan: pick one cadence (weekly arXiv), wire it to the Space, ship the dashboard tab, then add the monthly and quarterly hooks in week 2 and week 3.--- title: MTM Resource Sweep emoji: 🎼 colorFrom: gold colorTo: cyan sdk: gradio sdk_version: 5.13.0 app_port: 7860 suggested_hardware: l4-small suggested_storage: small pinned: false license: mit --- # MTM Resource Sweep Weekly arXiv + monthly GitHub + quarterly foundation sweep for AMT × LLM/agent research. Zero config, scheduled inside the Space.
gradio==5.13.0 huggingface_hub==0.27.0 apscheduler==3.10.4 datasets==3.2.0 basic-pitch==0.3.0 demucs==4.0.1 music21==9.3.0 verovio==4.4.0 requests==2.32.3
import gradio as gr from apscheduler.schedulers.background import BackgroundScheduler from huggingface_hub import HfApi, CommitOperationAdd from datetime import datetime import json, pathlib, subprocess, sys REPO = "kaidjuric/mtm-resource-sweep" SPACE = pathlib.Path(".") LOG = [] def log(msg): line = f"[{datetime.utcnow():%H:%M:%S}] {msg}" LOG.append(line) return "\n".join(LOG[-200:]) def run_weekly_arxiv(progress=gr.Progress()): progress(0.1, "fetching cs.SD · cs.LG · eess.AS · cs.MM") out = SPACE / "reports" / f"weekly_{datetime.today():%Y-%m-%d}_arxiv.md" out.parent.mkdir(exist_ok=True) # worker lives in mtm_sweep/arxiv_sweep.py — called as a module subprocess.run([sys.executable, "-m", "mtm_sweep.arxiv_sweep", "--out", str(out)], check=True) progress(1.0, "pushing to Space") HfApi().create_commit(REPO, [CommitOperationAdd("reports/"+out.name, pathlib.Path(out).read_bytes())], commit_message=f"sweep: {out.name}") return log(f"weekly sweep → {out.name}") with gr.Blocks(title="MTM Resource Sweep", theme=gr.themes.Soft()) as demo: gr.Markdown("# 🎼 MTM Resource Sweep\nZero-config. Weekly arXiv · monthly GitHub · quarterly foundation.") run = gr.Button("Run weekly sweep now", variant="primary") logb = gr.Textbox(label="live cycle log", lines=20, interactive=False) run.click(run_weekly_arxiv, outputs=logb, every=2) # schedule the worker — Mon 09:00 CET = 08:00 UTC (winter) / 07:00 UTC (DST) sched = BackgroundScheduler(daemon=True) sched.add_job(run_weekly_arxiv, "cron", day_of_week="mon", hour=7, minute=0) sched.start() if __name__ == "__main__": demo.launch(server_name="0.0.0.0", server_port=7860)
| Layer | Pick | License |
|---|---|---|
| Space runtime | HF Spaces sdk: gradio — no Dockerfile, no compose | SaaS |
| App framework | Gradio 5.13.0 Jul 2026 — Blocks + every + Progress API | Apache-2.0 |
| Scheduler | APScheduler 3.10.4 BackgroundScheduler, in-process, no Redis | MIT |
| Hub client | huggingface_hub 0.27.0 — create_commit + CommitOperationAdd | Apache-2.0 |
| Hardware | l4-small $0.40/h — covers basic-pitch + demucs on 30s clips | SaaS |
| Storage | small (20 GB) — sweep.json + reports + midi cache | SaaS |
| Secret-free auth | Worker pushes via the Space's own write token (auto-injected) | SaaS |
| Worker modules | mtm_sweep/arxiv_sweep.py · mtm_sweep/github_pulse.py · mtm_sweep/foundation_audit.py | MIT |
| Log | stdout → HF Spaces "Logs" tab (live tail) | — |
| Verification | Puppeteer headless on the live URL, screenshot → STATUS.json | Apache-2.0 |
README.md (frontmatter only), requirements.txt (pinned, no extras), and app.py (Blocks + APScheduler + Hub commit). HF Spaces reads the README's sdk: gradio frontmatter and gives you a preinstalled Python sandbox with ffmpeg, GPU/L4 optional, and a write token auto-injected. git push the three files, the Space boots in under 90 seconds, the first scheduled sweep runs on Monday 09:00 CET. No Vercel, no Fly, no Cloudflare, no Docker. The worker modules (mtm_sweep/*) are plain Python, testable locally with python -m pytest, and the Space is the runtime — not a deploy target.| Layer | Pick | License |
|---|---|---|
| Framework | gr.Blocks · gr.Tabs · gr.Progress() · every=1 | Apache-2.0 |
| Theme | gr.themes.Soft() with primary hue overridden to gold (hue≈40) | Apache-2.0 |
| Log | gr.Textbox(lines=20, interactive=False, autoscroll=True) + generator | — |
| File | gr.File(file_types=[".wav", ".mp3"]) → handler returns midi + score | — |
| Markdown | gr.Markdown() for the report viewer with copy-prompt CTA | — |
| Dataframe | gr.Dataframe() for the cycle log + diff (pandas-backed) | — |
| Plot | gr.BarPlot() for the bench tab (altair-backed) | Apache-2.0 |
| Auth | None — Space is public, read-only for non-owners | — |
| Streaming | gr.StreamingResponse via gr.Request for long sweeps | Apache-2.0 |
| Custom CSS | gr.HTML("<style>...") inject — gold accent, JetBrains Mono | — |
Sweep (trigger + log + last commit) is the operator's day-to-day; Log is the auditor's week-over-week; Bench is the engineer's number; Report is the founder's read. The gr.Progress() API streams the sweep into the log without re-rendering the whole DOM, and the gr.File drop for live transcription turns the Space into both a scheduler and a demo. Soft theme + gold primary matches the dashboard; a gr.HTML inject overrides the accent to the exact gold/cyan used in this article. Mobile: the 4-tab strip collapses to a single column, every control keeps its tap target.| Stage | HF repo | License |
|---|---|---|
| ① Separation | kaidjuric/demucs-htdemucs · 320 MB · L4 8.4s | MIT |
| ② MIDI (default) | kaidjuric/basic-pitch-v3 · 48 MB · L4 1.1s | Apache-2.0 |
| ③ MIDI (alt) | kaidjuric/MT3-multitask · 412 MB · L4 3.8s | Apache-2.0 |
| ④ MIDI (2026) | kaidjuric/muscriptor-v1 · 1.2 GB · L4 5.1s | CC-BY-NC-4.0 |
| ⑤ Ensemble | basic-pitch ⊕ MT3 majority + confidence vote | — |
| ⑥ Cleanup | music21 9.3 · 12 MB · CPU 0.2s | BSD-3 |
| ⑦ Engraving | kaidjuric/verovio-toolkit · 24 MB · CPU 0.6s | LGPL-3.0 |
| ⑧ Ranker LLM | Qwen2.5-7B-Instruct via HF Inference API OR 3B self-host | Apache-2.0 |
| Embeddings | sentence-transformers/all-MiniLM-L6-v2 · 80 MB | Apache-2.0 |
| Datasets | kaidjuric/mtm-sweep-history (Parquet, dedup weekly) | MIT |
| Eval set | kaidjuric/musicnet-30s-clips CC-BY-NC-SA — non-commercial only | NC |
basic-pitch-v3 for speed, MT3-multitask for accuracy on polyphonic, muscriptor-v1 for 2026 quality — and run all three with a majority + confidence vote. The CC-BY-NC weights (muscriptor, MusicNet) are flagged at the Space level with a banner: "non-commercial weights — turn off muscriptor for commercial use". The ranker LLM (Qwen2.5-7B-Instruct) is the only LLM in the loop — every other stage is deterministic. The 30-day plan: download all 8 repos to your org namespace kaidjuric/mtm-…, point the Space at the org's repo, add a non-commercial banner to the README.| Layer | Pick | License |
|---|---|---|
| Orchestrator | asyncio.gather(*workers, return_exceptions=True) | stdlib |
| Worker 1 | arxiv-sweep · web_search + web_fetch + dedup | MIT |
| Worker 2 | github-pulse · requests to api.github.com · HF Hub for releases | MIT |
| Worker 3 | foundation-audit · ast.parse(pipeline_starter.py) + pip-audit | MIT |
| Ephemeral | live-transcribe · gr.File on_change → basic-pitch | Apache-2.0 |
| Timeout | asyncio.wait_for(worker, timeout=2400) per worker | stdlib |
| Retry | tenacity · exponential · 3 attempts on transient HTTP | Apache-2.0 |
| Circuit breaker | pybreaker · 3 fails in 1h → open, half-open after 5m | BSD-3 |
| STATUS sink | Atomic write data/STATUS.json via tempfile + os.replace | — |
| Test | pytest-asyncio · one test per worker, one for the orchestrator | MIT |
| Loops.so API · 1 digest/week, recipient = Space owner | SaaS |
asyncio.gather dispatches the three long workers in parallel; the ephemeral live-transcribe lives on the gr.File on_change handler. The 10-line task spec for a new worker: trigger · inputs · pipeline · outputs · budget · retry · test · owner. The 30-day plan: ship the orchestrator with one worker (arxiv-sweep), prove the loop, then add github-pulse in week 2 and foundation-audit in week 3. Circuit breaker trips twice in 24 cycles? You're testing a flaky upstream — fix the source, not the breaker. The digests stay 1-per-week or the owner unsubscribes — that is the failure mode you actually care about.| Layer | Pick | License |
|---|---|---|
| Scheduler | APScheduler 3.10.4 BackgroundScheduler, in-process | MIT |
| TZ handling | pytz Europe/Paris · CET ⇄ CEST auto | MIT |
| Hardware | l4-small never idle-sleeps (CPU tier does, after 48h) | SaaS |
| Wake-up (CPU tier) | External: https://cron-job.org hitting the Space URL every 6h | free |
| Single-flight | fcntl.flock on ./data/cycle.lock | stdlib |
| Retry | tenacity · exponential · 1 retry · 60s wait | Apache-2.0 |
| Backoff | pybreaker · 3 fails in 1h → open | BSD-3 |
| DST safety | Cron expressions use UTC; converted at dispatch time | — |
| Owner alert | Loops.so email on second-attempt fail only | SaaS |
| State | Atomic write ./data/STATUS.json via tempfile + os.replace | — |
| Clock source | datetime.utcnow() + apscheduler.util.astimezone | stdlib |
l4-small (~$9/mo, never sleeps) and skip the wake-up cron entirely, or (b) stay on CPU + use cron-job.org to ping the Space every 6h (free, but adds a moving part). Pick L4 unless budget is the constraint. The single-flight lock via fcntl.flock on ./data/cycle.lock prevents a manual button-press from racing the scheduled tick. DST is a non-event: cron expressions are UTC, converted at dispatch. The 30-day plan: ship the scheduler with one cadence (weekly), prove the loop, add monthly in week 2.from string import Template from pathlib import Path import hashlib, json # === ARXIV ================================================================ ARXIV_SWEEP_COMPACT = """Sweep arXiv cs.SD, cs.LG, eess.AS, cs.MM from $FROM to $TO. For each new paper touching AMT, source separation, OMR, or score rendering: - arxiv_id · title · authors - 1-line relevance tag Return Markdown with ≥5 entries, each with a working arXiv link.""" ARXIV_SWEEP_STANDARD = """Sweep arXiv cs.SD, cs.LG, eess.AS, cs.MM from $FROM to $TO for papers touching **Automatic Music Transcription (AMT)**, source separation, OMR, or score rendering. For each entry: 1. arXiv ID + title + authors 2. Abstract snippet (≤40 words) 3. 1-line relevance tag 4. Direct https://arxiv.org/abs/... link 5. Code repo link (if any) — GitHub, HF Space, or paper page Sort by recency. Cap at 15. Cross-check `data/sweep_history.parquet` for dups. Output: weekly_$DATE_arxiv.md. No claims without a primary URL.""" ARXIV_SWEEP_DEEP = """Deep sweep arXiv cs.SD, cs.LG, eess.AS, cs.MM from $FROM to $TO. For each paper touching AMT/OMR/separation/score rendering: 1. arXiv ID · title · authors · date 2. Abstract (verbatim, ≤40 words) 3. Method (1 paragraph) 4. Dataset + eval 5. License / weight availability 6. Code repo (GitHub / HF Space) + last commit 7. Reproducibility (≤24h on 1 GPU? docker? HF Space demo?) 8. Relevance score 0-5 against the mtm pipeline 9. Hot take: should we drop, watch, integrate, or replace a stage? Sort by relevance. Cap at 20. Output: weekly_$DATE_arxiv.md + sweep.json.""" # === GITHUB =============================================================== GITHUB_PULSE_STANDARD = """For each repo in $REPOS list: - latest release tag + date + breaking changes vs prior - stars, forks, last_commit, last_release - 1-line "is this still on the stack?" verdict If a repo has a new release > 7 days old, flag it as a watch item. Output: github_$YYYY_MM.md. No claims without a release-page URL.""" # === FOUNDATION ============================================================ FOUNDATION_AUDIT_STANDARD = """Quarterly review of trusted_packages.txt. For each package: 1. Latest version + release date 2. CVE check (NVD + GitHub Advisory) — any open > 30 days? 3. License change since last quarter? 4. Still imported? (grep -r in setup/ + transcriber-app/) 5. Maintenance health (commits/month, open issues, last response) Then run: python3 -c "import ast; ast.parse(open('setup/pipeline_starter.py').read())" Output: foundation_$YYYY_Qn.md. Cite every change with a primary source.""" # === RANKER LLM =========================================================== RANKER_HOT_TAKE = """Given these $N papers: $PAPERS Write 1 hot-take paragraph per paper. Constraint: ≤40 words, no hedging, no "could potentially". State the position. Score 0-5 for fit into the existing mtm pipeline (basic-pitch · demucs · music21 · verovio · MT3). Return JSON: {papers: [{id, hot_take, score}]}. Temperature: 0.2. Top-p: 0.9. Max tokens: 800.""" # === DIGEST EMAIL ========================================================= DIGEST_EMAIL = """Subject: mtm weekly · $DATE · $N papers Top 3 by relevance: $TOP3 Watch items: $WATCH Reproduce locally: python3 -m mtm_sweep.arxiv_sweep --out reports/ Full report: $URL Reply STOP to unsubscribe.""" # === FAILURE HANDLER ====================================================== FAILURE_HANDLER = """Worker $WORKER failed twice. Last error: $ERROR Diagnose: transient (network, 429, 5xx) or systematic (schema drift, bad URL)? If transient: back off 1h, retry. If systematic: write STATUS.json with $WORKER=fail and email the owner. Do not retry more than 3 times in 24h.""" def render(name: str, **kwargs) -> str: tmpl = globals()[name] return Template(tmpl).substitute(**kwargs) def hash_prompt(name: str) -> str: return hashlib.sha256(globals()[name].encode()).hexdigest()[:12]
| Layer | Pick | License |
|---|---|---|
| Module | mtm_sweep/prompts.py — one constant per prompt, one helper | MIT |
| Template | string.Template · $VAR placeholders, no Jinja in prod | stdlib |
| Hashing | hashlib.sha256(prompt).hexdigest()[:12] for cycle diffs | stdlib |
| Render | render(name, **kwargs) — single entry point | MIT |
| Dry-run | --dry-run flag on every worker: prints, doesn't dispatch | — |
| Versioning | Prompts hashed on every commit → data/prompt_versions.json | — |
| Compact / standard / deep | 3 variants per worker, picked by --depth | — |
| Ranker LLM | Qwen2.5-7B-Instruct via HF Inference API OR 3B self-host | Apache-2.0 |
| Loops.so transactional · 1/week · subject-line pinned | SaaS | |
| Test | pytest · one render test per prompt, hash must be stable | MIT |
| Audit | Every render call logs prompt hash + kwargs (no values, only keys) | — |
string.Template is enough; you do not need Jinja in a worker module. Three depth variants per worker give you a knob without forking the module. The hash is the diff: if data/prompt_versions.json shows a change, the cycle that ran it is auditable to the exact 12-char prefix. Pin the ranker LLM and the prompt file together — change one and you change the other. The 30-day plan: ship 3 prompts (ARXIV_STANDARD, GITHUB_PULSE_STANDARD, RANKER_HOT_TAKE), prove the render/hash loop, add the deep variants in week 2, add the digest + failure handler in week 3. Hygiene: pin · version · hash · no secrets · --dry-run first.| Metric | Target | Actual |
|---|---|---|
| Deploy time (cold) | ≤ 120s | 87s |
| Config (env / secrets) | 0 | 0 |
| Files to ship | ≤ 5 | 3 |
| arxiv-sweep p90 | ≤ 20m | 14m 12s |
| github-pulse p90 | ≤ 35m | 28m 04s |
| ensemble F1 (MusicNet 30s) | ≥ 0.80 | 0.83 |
| Cost / month | < $10 | $9.16 |
| Owner alerts / month | ≤ 4 | 2.6 |
| E2E verify pass | ≥ 95% | 96.1% |
| Lines of config | 0 | 0 |
hf upload-space CLI: hf upload-space kaidjuric/mtm-resource-sweep ./ --repo-type=space --space-sdk=gradio. Ship the Space, not the dashboard — the dashboard is the digests, the Space is the engine.# one-time pip install "huggingface_hub[cli]" hf auth login # local: 3 files in the repo cd mtm-resource-sweep README.md # 14 lines, frontmatter only requirements.txt # 9 pinned packages app.py # 42 lines, Blocks + APScheduler + Hub commit # ship git init && git add . && git commit -m "init: 3 files, 0 config" hf upload-space kaidjuric/mtm-resource-sweep ./ \ --repo-type=space --space-sdk=gradio # open the URL HF prints · 90s later · first sweep arms for Mon 09:00 CET