diff --git a/.gitignore b/.gitignore index bd8ad4a4..2409084c 100755 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,8 @@ K8S.md # Machine-local Kernel Lab cluster profiles live under ~/.config/loom and must # never be copied into this repository. -/loom/kernel_hub/env.*.local \ No newline at end of file +/loom/kernel_hub/env.*.local +# Local runtime state and tool caches +.RUD/ +.pytest_cache/ +.ruff_cache/ diff --git a/docs/AUTO_RESEARCH_SYSTEM_DESIGN.md b/docs/AUTO_RESEARCH_SYSTEM_DESIGN.md index 3a3674d0..66f1b9de 100644 --- a/docs/AUTO_RESEARCH_SYSTEM_DESIGN.md +++ b/docs/AUTO_RESEARCH_SYSTEM_DESIGN.md @@ -19,7 +19,7 @@ flowchart TB subgraph LOOP["② Paper 工作台(每篇论文一个回合制状态机)"] direction TB - AUTHOR["Author Agent(Claude, tmux)
做实验(slurm GPU) + 写 LaTeX"] + AUTHOR["Author Agent(Cursor, tmux)
SSH 空闲 H100 做实验 + 写 LaTeX"] READY{"Readiness Gate
确定性检查"} PANEL["Reviewer Panel
三模型读编译后 PDF
最低分定档"] STOP{"停止条件
达标 / 满轮 / 平台期"} @@ -60,7 +60,7 @@ flowchart TB I1["tmux Agent 池
+ Web 实时面板"] I2["Web UI / API
:8766 + 公网隧道"] I3["Hot Restart
不断任务换代码"] - I4["slurm H100 集群"] + I4["GPU Scout
轮询空闲 H100 + SSH 直跑"] end SPAWN --> DG["🧑 Draft Human Gate
批准骨架稿"] --> LOOP @@ -105,9 +105,11 @@ flowchart TB 每一轮(round N)内部的固定节拍: -1. **Author Agent**(Claude,常驻 tmux,工作在自己的 worktree)收到本轮 Prompt: +1. **Author Agent**(Cursor Agent,当前默认 `gpt-5.6-sol-max-fast`,常驻 tmux, + 工作在自己的 worktree)收到本轮 Prompt: 上一轮的评审报告 + 方法论技能(AR-AUTHOR)+ 图片技能菜单 + GPU 集群使用规范。 - 它做实验(提交 slurm GPU 任务)、改论文、重编译,最后写 `author.md` 作为完成信号。 + 它做实验(读取 GPU Scout 后直接 SSH 到空闲 H100)、改论文、重编译, + 最后写 `author.md` 作为完成信号。 2. **Readiness Gate(确定性代码)**:编译必须干净;不允许任何 `\ARnum`/TODO/`??` 占位; 各章节实质完整;page-one 总览图必须存在;所有被引用的图文件存在;引用无悬空。 不合格 → 列出失败清单原样打回 Author,本轮重做,不消耗评审。 @@ -128,7 +130,8 @@ flowchart TB - `AR-STUDIO.md` / `AR-AUTHOR.md` / `AR-REVIEWER.md`:三种角色的完整方法论; - `figures/teaser-figure-1..4`、`results-figure-1..2`、`checkbib`:画图与查引用的具体做法 (从纯代码矢量图到 AI 生成再到混合方案,多风格可选,作者按需取用); -- `GPU-RESOURCES.md`:集群使用规范(禁止登录节点跑模型、sbatch 模板、防 GPU 被占的 requeue 守卫); +- `GPU-RESOURCES.md`:集群使用规范(禁止登录节点跑模型;读取 GPU Scout 的实时空闲清单, + 直接 SSH 到 compute node 并用 `CUDA_VISIBLE_DEVICES` 启动作业); - `paper-rebuttal/SKILL.md`、`paper-rebuttal-delivery/SKILL.md`:rebuttal 起草与终稿交付的方法论。 ## 5. 模块四:Rebuttal Factory(两级结构 + 双人工 Gate) @@ -167,7 +170,9 @@ flowchart TB Agent 无感知; - **监控循环**:`delivery_monitor.py` 等看门狗把"Agent 完成 → 校验 → 验收 → 喂回失败报告"的 节拍自动化,出结果或卡死才通知人; -- **slurm H100 集群**:实验全部走 sbatch;登录节点只做聚合和画图。 +- **GPU Scout + H100 集群**:守护进程每分钟 SSH 各 compute node 读取真实 + `nvidia-smi`,发布空闲 `node:gpu` 清单;Agent 不走不可靠的 Slurm 排队, + 而是二次确认显存后直接 SSH 启动作业。登录节点只做聚合和画图。 --- @@ -206,3 +211,68 @@ Web/编排 loom/web.py 论文实例 /.RUD//{ar.json, rounds/, work/manuscript/main.pdf} Rebuttal 实例 /rebuttal-output/{state.json, responses/, delivery/attempts//deliverables/} ``` + +--- + +## 10. 对外项目介绍与当前状态(2026-08-17) + +### 10.1 可以怎样向另一个团队介绍 + +**Loom Auto Research** 是一个面向长周期科研任务的 Agent 编排系统。它不是让一个 +聊天模型一次性“写论文”,而是把科研过程拆成可恢复、可审计的状态机: + +1. 从会议往届获奖论文、oral、热点和研究者已有能力中生成可证伪的选题; +2. 每个选题孵化为隔离的代码与 LaTeX 工作区,由长期运行的 Cursor Agent 做实验和写作; +3. Python Readiness Gate 先拦截编译错误、占位符、缺图和虚假完成; +4. GPT、Claude、Grok 三个独立 reviewer 只读编译后的 PDF,按最低分推动下一轮修改; +5. 稳定后进入 Delivered;投稿后还可进入 Rebuttal Factory,生成回复、修订稿和提交 bundle。 + +项目的核心技术价值在于: + +- **可靠的长周期 Agent orchestration**:任务跨小时/天运行,进程、服务或会话重启后可从磁盘状态恢复; +- **确定性控制 + 模型创造力**:状态转换、门禁、页数、哈希和完成条件由代码控制,模型只负责研究内容; +- **跨模型 eval**:执行者与评审者隔离,三个模型只看最终 PDF,避免作者自评; +- **真实计算闭环**:Agent 自己写实验代码,GPU Scout 分配实际空闲 H100,结果再写回论文; +- **artifact-level verification**:批准绑定 PDF/文本哈希,任何修改都会令旧批准失效; +- **human-in-the-loop**:系统可以全自动运行,但保留关键 Gate 和实时 tmux 面板供人检查或介入。 + +### 10.2 当前实现和实跑规模 + +| 项目状态 | 当前情况 | +|---|---| +| 产品形态 | Research Factory、Paper 工作台、Rebuttal Factory、Web UI/API、实时 tmux 面板 | +| 会议支持 | ICLR、NeurIPS、ICML、COLM、WACV;WACV 支持 Algorithms/Applications/Datasets track | +| 当前实验 | 同时运行 8 篇 WACV 2027 + 8 篇 WSDM 2027 paper | +| 当前结果 | 截至本次快照,4/16 已 Delivered;其余处于第 1–7 轮 Author/Reviewer 循环 | +| 实时状态 | `docs/notes/zhizhou/WACV_WSDM_PAPER_PROGRESS.md` 每分钟按实际状态更新 | +| Rebuttal 验证 | 已在两篇 WACV rebuttal package 上跑通修订稿、一页回复、supplement、重编译和三模型图片验收 | +| 运行基础设施 | 16 个长期 Agent pane、两套 autopilot、GPU Scout、可热重启的 8766 Web 服务 | + +这仍是一个研究原型,而不是“科研已被完全自动化”的结论。目前最重要的下一步是: +系统化比较自动生成论文与人工基线的科学质量、减少 agent 过度扩展实验范围、提高 reviewer +评分与人类专家评分的一致性,并把当前针对单个研究者的能力画像产品化为可复用 profile。 + +### 10.3 可以直接发送的英文消息草稿 + +> Hi [Name] — I wanted to ask whether there might be an opportunity to intern +> with your team during the fall semester. +> +> I have been building **Loom Auto Research**, an agentic system for long-horizon +> research workflows. It turns a venue and research direction into concrete +> hypotheses, runs real experiments on GPUs, writes and compiles papers, and +> iterates through an independent GPT/Claude/Grok PDF-review panel. A +> deterministic state machine controls readiness checks, recovery, human gates, +> and artifact hashes, so the system can run for days without treating an LLM's +> claim of completion as ground truth. I also built a related rebuttal pipeline +> that produces revised papers, one-page responses, supplements, and validated +> submission artifacts. +> +> In the current evaluation, Loom is running 16 concurrent WACV/WSDM research +> projects; four have reached the Delivered stage and the rest are progressing +> through automated author/reviewer rounds. The project has given me hands-on +> experience with reliable agent orchestration, long-horizon task recovery, +> multi-model evaluation, GPU execution, and human-in-the-loop system design. +> +> I think this work may overlap with your team's interests in [team area]. Would +> you be open to a short conversation about whether I could join your team as a +> fall intern? I would be happy to share a demo and the system design. diff --git a/docs/notes/wsdm2027/wsdm 3 paper.md b/docs/notes/wsdm2027/wsdm 3 paper.md new file mode 100644 index 00000000..c4227c84 --- /dev/null +++ b/docs/notes/wsdm2027/wsdm 3 paper.md @@ -0,0 +1,121 @@ +# wsdm 3 paper + +This file contains three WSDM papers. The titles, abstracts, and subject areas +are plain-text, copy-paste-ready CMT registration fields. + +- Enter authors, conflicts, and other administrative metadata separately. +- Internal automated reviews are included only for reference; do not paste + them into CMT. + +## Registration Summary + +| ID | Status | Final round | Primary subject area | +|---|---|---:|---| +| `wsdm-05` | Delivered | 6 | Web Mining and Content Analysis → Web recommender systems and algorithms | +| `wsdm-11` | Delivered | 5 | Foundation Models and Agentic Systems → Evaluation and benchmarking of foundation models in search/mining | +| `wsdm-12` | Delivered | 8 | Web Mining and Content Analysis → Web recommender systems and algorithms | + +## 1. wsdm-05 + +**Status:** Delivered, Round 6 + +### Title + +When Does One Step Suffice? A Four-Gate Audit of Diffusion Recommendation Distillation + +### Subject Areas + +- **Primary:** Web Mining and Content Analysis → Web recommender systems and algorithms +- **Secondary:** Web Mining and Content Analysis → Scalable algorithms for mining web data, opinion mining and sentiment analysis +- **Secondary:** Web Search → Search benchmarking and evaluation +- **Secondary:** Privacy, Fairness, Interpretability → Model and algorithm transparency + +### Abstract + +A one-call deployment claim bundles four different statements: the teacher is competent, iterative sampling helps, the student preserves the teacher, and the result has serving value. We introduce a four-gate audit that tests these claims separately and scopes every decision to a checkpoint and protocol. + +Using a common full-catalog harness over MovieLens-1M and Steam, together with a native Amazon Beauty DiffuRec reproduction, we compare diffusion teachers, iterative sampling, one-pass controls, and endpoint regression under matched evaluation. Utility-tuned SASRec outperforms the audited teachers in all 16 metric-level comparisons, and DDIM-1 outperforms the multi-step endpoint in 15 of 16; the remaining comparison is inconclusive. Endpoint regression sometimes preserves aggregate utility under simultaneous noninferiority tests, yet exact teacher top-10 set identity never exceeds 20.5%. The contribution is a falsifiable deployment contract and checkpoint-level evidence, not a broad claim that one step or many steps universally wins. + +### Internal Automated Review — Do Not Paste into CMT + +Round 6 panel score: **4/10 · soundness 2/4 · weak reject**. + +| Reviewer | Rating | Soundness | Presentation | Contribution | Recommendation | +|---|---:|---:|---:|---:|---| +| GPT-5.6 Sol | 5/10 | 3/4 | 3/4 | 2/4 | Weak reject | +| Claude Fable 5 | 5/10 | 3/4 | 3/4 | 2/4 | Borderline | +| Cursor Grok 4.5 | 4/10 | 2/4 | 2/4 | 2/4 | Weak reject | + +## 2. wsdm-11 + +**Status:** Delivered, Round 5 + +### Title + +Query-Term Repetition Repels LLM Selectors from Weak Result Cards: A Controlled Audit + +### Subject Areas + +- **Primary:** Foundation Models and Agentic Systems → Evaluation and benchmarking of foundation models in search/mining +- **Secondary:** Web Search → Search user behavior and log analysis; Search user interfaces and interaction +- **Secondary:** Foundation Models and Agentic Systems → Retrieval, indexing, and ranking with foundation models +- **Secondary:** Web Search → Query analysis and query processing +- **Secondary:** Privacy, Fairness, Interpretability → Model and algorithm transparency + +### Abstract + +Implicit feedback is useful only when selection remains aligned with landing-page relevance. We audit one proposed surface intervention for deterministic LLM selectors: replacing generic metadata with repeated query terms in weak result cards. In paired result-card displays, lower-relevance cards contain either one copy or repeated copies of the same query terms, while answer content, landing page, judgment, card length, unique term set, topic, rank schedule, and all higher-relevance cards remain fixed. + +Across four confirmatory selectors, repetition reduces false-choice rates by 2.3–6.2 percentage points, with Holm-adjusted p < .001 in every case. A short warning about repeated query words produces no detectable interaction with this effect and therefore does not explain an earlier hardened-prompt contrast. Exact frequency does not explain an earlier synthetic same-query effect that also changed topic and plausibility. We make no human-click claim, and we treat an earlier answer-rewrite audit without independent regrades as conditional evidence only. + +### Internal Automated Review — Do Not Paste into CMT + +Round 5 panel score: **4/10 · soundness 3/4 · weak reject**. + +| Reviewer | Rating | Soundness | Presentation | Contribution | Recommendation | +|---|---:|---:|---:|---:|---| +| GPT-5.6 Sol | 5/10 | 2/4 | 3/4 | 2/4 | Weak reject | +| Claude Fable 5 | 5/10 | 3/4 | 2/4 | 2/4 | Weak reject | +| Cursor Grok 4.5 | 4/10 | 3/4 | 3/4 | 2/4 | Weak reject | + +## 3. wsdm-12 + +**Status:** Delivered, Round 8 + +### Title + +How Small Can You Go? Spectral Bounds for Recommendation Subsets + +### Subject Areas + +- **Primary:** Web Mining and Content Analysis → Web recommender systems and algorithms +- **Secondary:** Web Mining and Content Analysis → Large-scale graph analysis +- **Secondary:** Web Mining and Content Analysis → Scalable algorithms for mining web data, opinion mining and sentiment analysis +- **Secondary:** Privacy, Fairness, Interpretability → Model and algorithm transparency + +### Abstract + +How many original-identity interactions are necessary—and how many are actually sufficient—to preserve a collaborative-filtering propagation subspace? We separate these questions. For a source graph's rank-r normalized-biadjacency frame, every unweighted edge subset incurs joint projector loss at least the source leverage mass outside its retained user and item coordinates. Requiring an identifiable cutoff adds a component-multiplicity floor. The combined floor is asymptotically attainable on a block-complete family. + +Real recommendation graphs are different. We audit three public graphs at ranks 2, 4, and 8. At rank eight, the necessary floors retain at most 2.1% of edges, while the first observed identifiable witnesses require at least 33%. A connectivity-preserving construction also remains far above the floor. The theorem therefore rules out ultra-small subsets but does not predict the attainable projector-collapse budget on these graphs. We report a lower-to-upper interval rather than call the necessary floor tight. The result applies to unweighted same-identity subsets, not synthetic identities, reweighted sparsifiers, arbitrary finite codes, or ranking utility. + +### Internal Automated Review — Do Not Paste into CMT + +Round 8 panel score: **4/10 · soundness 2/4 · weak reject**. + +| Reviewer | Rating | Soundness | Presentation | Contribution | Recommendation | +|---|---:|---:|---:|---:|---| +| GPT-5.6 Sol | 5/10 | 3/4 | 3/4 | 2/4 | Weak reject | +| Claude Fable 5 | 5/10 | 3/4 | 3/4 | 2/4 | Weak reject | +| Cursor Grok 4.5 | 4/10 | 2/4 | 2/4 | 2/4 | Weak reject | + +## CMT Registration Checklist + +For each paper: + +- Copy the title exactly from the `Title` field. +- Copy both abstract paragraphs into the CMT abstract field. +- Select the listed primary subject area first, then the suggested secondary + areas that CMT permits. +- Enter authors, affiliations, conflicts, and contact information separately. +- Do not paste the internal automated-review section into CMT. diff --git a/docs/notes/wsdm2027/wsdm-5-paper.md b/docs/notes/wsdm2027/wsdm-5-paper.md new file mode 100644 index 00000000..a12e09ca --- /dev/null +++ b/docs/notes/wsdm2027/wsdm-5-paper.md @@ -0,0 +1,205 @@ +# wsdm 5 paper + +This file contains five WSDM papers. The titles, abstracts, and subject areas +are plain-text, copy-paste-ready CMT registration fields. + +- Enter authors, conflicts, and other administrative metadata separately. +- Internal automated reviews are included only for reference; do not paste + them into CMT. +- `wsdm-06` is still in Round 3. Its title is ready, but its abstract and review + must be refreshed after delivery. + +## Registration Summary + +| CMT submission ID | ID | Status | Final/current round | Primary subject area | +|---:|---|---|---:|---| +| 1779 | `wsdm-02` | Delivered | 6 | Web Search → Algorithms for web-scale search, distributed search, metasearch, peer-to-peer search | +| 1797 | `wsdm-03` | Delivered | 10 | Foundation Models and Agentic Systems → Retrieval, indexing, and ranking with foundation models | +| 1996 | `wsdm-04` | Delivered | 7 | Foundation Models and Agentic Systems → Evaluation and benchmarking of foundation models in search/mining | +| 1999 | `wsdm-06` | In progress; abstract provisional | 3 | Web Mining and Content Analysis → Web recommender systems and algorithms | +| 2002 | `wsdm-09` | Delivered | 6 | Web Mining and Content Analysis → Web recommender systems and algorithms | + +## 1. wsdm-02 + +**CMT submission ID:** `1779` + +**Status:** Delivered, Round 6 + +### Title + +Risk-Sensitive Diversification Without an Oracle: Valid Certificates Need Not Identify Better Rankings + +### Subject Areas + +- **Primary:** Web Search → Algorithms for web-scale search, distributed search, metasearch, peer-to-peer search +- **Secondary:** Web Search → Search benchmarking and evaluation +- **Secondary:** Web Search → Query analysis and query processing +- **Secondary:** Privacy, Fairness, Interpretability → Model and algorithm transparency + +### Abstract + +Does a valid robust tail-risk certificate identify a better diversified ranking when the intent prior is estimated? Not necessarily. For finite-intent VRisk, fixed-ranking L1 error ε changes risk by at most min{1, ε/(2β)}, sharply, while a standard count radius can collapse robust CVaR to minimax. Even with a perfectly estimated prior and valid coverage, the resulting saturated minimizer and upper-bound gate can incur regret arbitrarily close to one. This separates certificate validity from decision usefulness. + +A frozen-proposal audit across retrieval and recommendation benchmarks supports the distinction. Exact same-prior comparison accepts 17.9% of proposals in the primary NTCIR synthetic-prior experiment, while the evaluated real-estimator settings certify no intervention and matched robust-versus-minimax effects are mostly negligible. Validity, informativeness, and decision benefit are therefore distinct properties. + +### Internal Automated Review — Do Not Paste into CMT + +Round 6 panel score: **4/10 · soundness 3/4 · weak reject**. + +| Reviewer | Rating | Soundness | Presentation | Contribution | Recommendation | +|---|---:|---:|---:|---:|---| +| GPT-5.6 Sol | 5/10 | 3/4 | 3/4 | 2/4 | Weak reject | +| Claude Fable 5 | 6/10 | 3/4 | 3/4 | 2/4 | Weak accept | +| Cursor Grok 4.5 | 4/10 | 3/4 | 2/4 | 2/4 | Weak reject | + +## 2. wsdm-03 + +**CMT submission ID:** `1797` + +**Status:** Delivered, Round 10 + +### Title + +Computational Limits of Finite-Beam Generative Retrieval with Semantic IDs + +### Subject Areas + +- **Primary:** Foundation Models and Agentic Systems → Retrieval, indexing, and ranking with foundation models +- **Secondary:** Web Search → Algorithms for web-scale search, distributed search, metasearch, peer-to-peer search +- **Secondary:** Web Mining and Content Analysis → Web recommender systems and algorithms +- **Secondary:** Web Search → Search benchmarking and evaluation +- **Secondary:** Web Mining and Content Analysis → Scalable algorithms for mining web data, opinion mining and sentiment analysis + +### Abstract + +Semantic-ID retrieval replaces corpus-wide scoring with autoregressive trie search, mixing representation and search error. For canonical Gibbs distillation, we separate them exactly: every dense score vector has a unique positive trie-local factorization with the same exhaustive leaf order, while prefix log-mass equals best-descendant score plus log effective multiplicity. This yields an exact margin-mass boundary. For every fixed alphabet K and width b, an injective N = bK + 1 family makes all widths through b lose the unique optimum. The limit is conditional on this calibration, not universal over learned trees. + +We then jointly train item tables, history GRUs, and rank-32 local decoders under bitwise-matched initialization. On Amazon Beauty with 12,101 items, five seeds, and 1,024 users, canonical exhaustive retrieval retains 67.35% teacher top-10 overlap, but width 10 preserves only 53.29% of its own exact top-10 set. Rank-preserving τ/2 sharpening raises this to 83.32% while retaining 67.03% exhaustive teacher overlap; exact max-node training and direct scoring reach 95.82%. Task intervals overlap, so the evidence establishes a finite-search mechanism and fidelity interventions, not a recommendation gain, production prevalence, or superiority over ANN, lookahead, or retention methods. + +### Internal Automated Review — Do Not Paste into CMT + +Round 10 panel score: **5/10 · soundness 3/4 · weak reject**. + +| Reviewer | Rating | Soundness | Presentation | Contribution | Recommendation | +|---|---:|---:|---:|---:|---| +| GPT-5.6 Sol | 5/10 | 3/4 | 3/4 | 2/4 | Weak reject | +| Claude Fable 5 | 6/10 | 3/4 | 2/4 | 3/4 | Weak accept | +| Cursor Grok 4.5 | 5/10 | 3/4 | 3/4 | 2/4 | Weak reject | + +## 3. wsdm-04 + +**CMT submission ID:** `1996` + +**Status:** Delivered, Round 7 + +### Title + +Did the Judge See the Answer? A Crossed Dose–Response Audit of LLM Relevance Judgments + +### Subject Areas + +- **Primary:** Foundation Models and Agentic Systems → Evaluation and benchmarking of foundation models in search/mining +- **Secondary:** Web Search → Search benchmarking and evaluation +- **Secondary:** Foundation Models and Agentic Systems → Retrieval, indexing, and ranking with foundation models +- **Secondary:** Privacy, Fairness, Interpretability → Model and algorithm transparency +- **Secondary:** Foundation Models and Agentic Systems → LLMs and multimodal foundation models for web tasks + +### Abstract + +If relevance labels leak into an LLM judge, improved agreement can arise from learning passage-grade bindings or merely from absorbing a topic's label marginal. Held-out passages alone do not separate these mechanisms. We run a second-collection replication on all 50 TREC-COVID topics, constructing every train and held-out set to contain three grades. True-label exposure is compared with two zero-match wrong-label cycles that preserve the same passages and grade histogram. Two disjoint splits, two derangements, three optimizer seeds, and four fixed open judges yield 3,600 training trajectories, each measured after 5, 10, 20, and 40 updates. + +At the prespecified 40-update endpoint, the held-out true-minus-wrong probability-weighted agreement contrast is 0.078 (95% CI [0.061, 0.094], p < 0.0001). The contrast grows from 0.011 at five updates to 0.078 at forty, with a log-dose slope of 0.022 [0.017, 0.027]. All four unadjusted model-specific intervals exclude zero, but effects range from 0.007 for SmolLM2-1.7B to 0.121 for Qwen2.5-7B, and the model-by-condition interaction is significant. These results establish dose-dependent passage-binding susceptibility under controlled LoRA exposure, not natural pretraining contamination, benchmark membership, or a system-ranking consequence. + +### Internal Automated Review — Do Not Paste into CMT + +Round 7 panel score: **5/10 · soundness 3/4 · weak reject**. + +| Reviewer | Rating | Soundness | Presentation | Contribution | Recommendation | +|---|---:|---:|---:|---:|---| +| GPT-5.6 Sol | 5/10 | 3/4 | 3/4 | 2/4 | Weak reject | +| Claude Fable 5 | 6/10 | 3/4 | 3/4 | 2/4 | Weak accept | +| Cursor Grok 4.5 | 5/10 | 3/4 | 3/4 | 2/4 | Borderline | + +## 4. wsdm-06 + +**CMT submission ID:** `1999` + +**Status:** Round 3 in progress. The abstract and review below are provisional. + +### Title + +Tokenizers That Peek: Test-Set Leakage in Semantic-ID Generative Recommendation + +### Subject Areas + +- **Primary:** Web Mining and Content Analysis → Web recommender systems and algorithms +- **Secondary:** Foundation Models and Agentic Systems → Retrieval, indexing, and ranking with foundation models +- **Secondary:** Foundation Models and Agentic Systems → Evaluation and benchmarking of foundation models in search/mining +- **Secondary:** Web Search → Search benchmarking and evaluation +- **Secondary:** Privacy, Fairness, Interpretability → Model and algorithm transparency + +### Abstract + +Semantic IDs replace atomic item labels with learned token sequences, making the tokenizer part of a recommender's fitted state. If its collaborative features consume held-out interactions, test labels can alter item-to-code assignments even when generator training remains clean. We isolate this channel under a global timeline: examples, targets, candidates, architecture, and seed are paired, and only tokenizer interaction scope changes. + +Under equal-budget validation tuning, peeking raises Recall@10 by 0.589 percentage points (95% paired-seed interval [0.535, 0.642]) across three Amazon categories, two interaction-aware paths, and 15 seeds; this is 25.1% of clean performance. The content control changes by exactly 0.000, while a non-discretized Continuous-SVD control has an even larger positive gap, so the channel does not require tokenization. Removing scored target edges in all six cells leaves a macro interval spanning zero. The target component exceeds the pooled matched-removal null, but not its 90%-target-overlap stratum; the evidence supports overlap sensitivity, not exact-edge uniqueness. An immutable audit additionally finds one documented test-selected collaborative-feature path whose shipped tensor lineage is unresolved; we do not infer impact on a published score or prevalence. + +### Internal Automated Review — Do Not Paste into CMT + +Round 2 panel score: **5/10 · soundness 3/4 · weak reject**. Round 3 is still +running, so this score is provisional. + +| Reviewer | Rating | Soundness | Presentation | Contribution | Recommendation | +|---|---:|---:|---:|---:|---| +| GPT-5.6 Sol | 5/10 | 3/4 | 3/4 | 2/4 | Weak reject | +| Claude Fable 5 | 6/10 | 3/4 | 3/4 | 3/4 | Weak accept | +| Cursor Grok 4.5 | 5/10 | 3/4 | 3/4 | 2/4 | Borderline | + +### Registration Action Required + +After `wsdm-06` is delivered, refresh its abstract, round, status, and automated +review before finalizing CMT registration. + +## 5. wsdm-09 + +**CMT submission ID:** `2002` + +**Status:** Delivered, Round 6 + +### Title + +Information Before Scale: Sample and Rank Frontiers for Walsh Collaborative Filtering + +### Subject Areas + +- **Primary:** Web Mining and Content Analysis → Web recommender systems and algorithms +- **Secondary:** Web Mining and Content Analysis → Scalable algorithms for mining web data, opinion mining and sentiment analysis +- **Secondary:** Privacy, Fairness, Interpretability → Model and algorithm transparency + +### Abstract + +Empirical recommender scaling curves do not distinguish insufficient output rank from insufficient information to identify a user's latent preference. We separate these resources on a synthetic Walsh collaborative-filtering family. For a revealed user-Walsh assignment, we derive an exact finite minimax rank rule. We then withhold that assignment while keeping the same signed permutation of the Walsh table. + +Although the posterior now couples users through perfect matchings, we derive its exact finite conditional frontier and show that removing the assignment raises the fixed-accuracy sample scale from constant to logarithmic. Under binary-symmetric label noise, consistency has the sharp first-order threshold NpCq = log2 N, where Cq = 1 - h2(q). On matched sparse transcripts at N = 128, a validation-selected rank-N/2 matrix-factorization model reaches risk 0.964 against the 0.568 information frontier, exposing a substantial gap between optimization and information limits. These are architecture-relative results for a public Walsh dictionary, not an industrial scaling law. + +### Internal Automated Review — Do Not Paste into CMT + +Round 6 panel score: **5/10 · soundness 2/4 · weak reject**. + +| Reviewer | Rating | Soundness | Presentation | Contribution | Recommendation | +|---|---:|---:|---:|---:|---| +| GPT-5.6 Sol | 6/10 | 3/4 | 3/4 | 3/4 | Weak accept | +| Claude Fable 5 | 6/10 | 3/4 | 3/4 | 2/4 | Weak accept | +| Cursor Grok 4.5 | 5/10 | 2/4 | 3/4 | 2/4 | Weak reject | + +## CMT Registration Checklist + +For each paper: + +- Copy the title exactly from the `Title` field. +- Copy both abstract paragraphs into the CMT abstract field. +- Select the listed primary subject area first, then the suggested secondary + areas that CMT permits. +- Enter authors, affiliations, conflicts, and contact information separately. +- Do not paste the internal automated-review section into CMT. +- Refresh all `wsdm-06` fields after Round 3 delivery. diff --git a/docs/notes/zhizhou/WACV_WSDM_PAPER_PROGRESS.md b/docs/notes/zhizhou/WACV_WSDM_PAPER_PROGRESS.md new file mode 100644 index 00000000..3730ac47 --- /dev/null +++ b/docs/notes/zhizhou/WACV_WSDM_PAPER_PROGRESS.md @@ -0,0 +1,22 @@ +# WACV / WSDM Paper Progress + +Updated: 2026-08-17 14:07 PDT + +| Venue | ID | Paper | Stage | Round | Latest score | Agent | Folder | +|---|---|---|---|---:|---:|---|---| +| WACV | wacv-fit-01 | 扩散得分平滑度驱动的开集测试时自适应 | Author/reviewer loop | 7/10 | 4/10 | Working | `/data/shared/zhizhousha/workspace/loom-project/loom-claude-paper/research-factory/.RUD/wacv2027-diffusion-score-smoothness-for-open-set-test-time-adaptation` | +| WACV | wacv-fit-02 | 得分统计量的扩散视频取证 | Delivered | 7/10 | 4/10 | Done | `/data/shared/zhizhousha/workspace/loom-project/loom-claude-paper/research-factory/.RUD/wacv2027-score-statistic-forensics-for-diffusion-generated-video-detection` | +| WACV | wacv-fit-03 | 手术场景的开集识别与安全弃权 | Author/reviewer loop | 3/10 | 4/10 | Working | `/data/shared/zhizhousha/workspace/loom-project/loom-claude-paper/research-factory/.RUD/wacv2027-open-set-recognition-with-safe-abstention-for-surgical-scene-understan` | +| WACV | wacv-fit-04 | 差分隐私的注视估计个性化 | Author/reviewer loop | 3/10 | 4/10 | Working | `/data/shared/zhizhousha/workspace/loom-project/loom-claude-paper/research-factory/.RUD/wacv2027-differentially-private-personalization-for-gaze-estimation` | +| WACV | wacv-fit-08 | 检索记忆智能体的长程序化视频理解 | Author/reviewer loop | 7/10 | 4/10 | Working | `/data/shared/zhizhousha/workspace/loom-project/loom-claude-paper/research-factory/.RUD/wacv2027-retrieval-memory-agents-for-long-form-procedural-video-understanding` | +| WACV | wacv-fit-14 | 表示层攻击审计合成图像取证器 | Author/reviewer loop | 7/10 | 5/10 | Working | `/data/shared/zhizhousha/workspace/loom-project/loom-claude-paper/research-factory/.RUD/wacv2027-presentation-only-attacks-for-auditing-synthetic-image-forensics-detec` | +| WACV | wacv-fit-16 | 可验证奖励强化学习的视频异常因果推理 | Author/reviewer loop | 7/10 | 4/10 | Working | `/data/shared/zhizhousha/workspace/loom-project/loom-claude-paper/research-factory/.RUD/wacv2027-verifiable-reward-rl-for-causal-video-anomaly-reasoning` | +| WACV | wacv-fit-17 | 低秩几何可证保证的分布外检测 | Author/reviewer loop | 5/10 | 5/10 | Working | `/data/shared/zhizhousha/workspace/loom-project/loom-claude-paper/research-factory/.RUD/wacv2027-provable-low-rank-feature-geometry-for-out-of-distribution-detection` | +| WSDM | wsdm-02 | 没有预言机的风险敏感多样化:意图估计误差如何侵蚀最差情况保证 | Delivered | 6/10 | 4/10 | Done | `/data/shared/zhizhousha/workspace/loom-project/loom-claude-paper/research-factory/.RUD/wsdm2027-risk-sensitive-diversification-without-an-oracle` | +| WSDM | wsdm-03 | Semantic ID 上生成式检索的计算极限 | Author/reviewer loop | 7/10 | 5/10 | Working | `/data/shared/zhizhousha/workspace/loom-project/loom-claude-paper/research-factory/.RUD/wsdm2027-semantic-id-computational-limits-of-generative-retrieval-over-semantic` | +| WSDM | wsdm-04 | 判官读过答案吗:LLM 相关性判断中的知识截断污染 | Author/reviewer loop | 5/10 | 5/10 | Working | `/data/shared/zhizhousha/workspace/loom-project/loom-claude-paper/research-factory/.RUD/wsdm2027-llm-knowledge-cutoff-contamination-in-llm-relevance-judgments` | +| WSDM | wsdm-05 | 一步就够:带可证明误差界的扩散推荐蒸馏 | Author/reviewer loop | 6/10 | 5/10 | Working | `/data/shared/zhizhousha/workspace/loom-project/loom-claude-paper/research-factory/.RUD/wsdm2027-one-step-suffices-diffusion-recommendation-distillation-with-provable` | +| WSDM | wsdm-06 | 会偷看的分词器:Semantic ID 构建导致的生成式推荐测试集泄漏 | Author/reviewer loop | 3/10 | 5/10 | Working | `/data/shared/zhizhousha/workspace/loom-project/loom-claude-paper/research-factory/.RUD/wsdm2027-semantic-id-tokenizers-that-peek-test-set-leakage-in-semantic-id-gener` | +| WSDM | wsdm-09 | 规模是必要的吗:协同过滤的模型容量下界 | Delivered | 6/10 | 5/10 | Done | `/data/shared/zhizhousha/workspace/loom-project/loom-claude-paper/research-factory/.RUD/wsdm2027-is-scale-necessary-capacity-lower-bounds-for-collaborative-filtering` | +| WSDM | wsdm-11 | 当网页被 LLM 写满:点击相关性代理还成立吗 | Delivered | 5/10 | 4/10 | Done | `/data/shared/zhizhousha/workspace/loom-project/loom-claude-paper/research-factory/.RUD/wsdm2027-llm-when-the-web-is-llm-written-auditing-click-signals-as-relevance-pr` | +| WSDM | wsdm-12 | 最多能压多小:推荐图压缩的样本复杂度下界 | Delivered | 8/10 | 4/10 | Done | `/data/shared/zhizhousha/workspace/loom-project/loom-claude-paper/research-factory/.RUD/wsdm2027-how-small-can-you-go-sample-complexity-limits-of-graph-condensation-fo` | diff --git a/docs/notes/zhizhou/WSDM2026_BEST_ORAL_SUMMARY.md b/docs/notes/zhizhou/WSDM2026_BEST_ORAL_SUMMARY.md new file mode 100644 index 00000000..51632e39 --- /dev/null +++ b/docs/notes/zhizhou/WSDM2026_BEST_ORAL_SUMMARY.md @@ -0,0 +1,89 @@ +# WSDM 2026 获奖与代表性论文的共通点 + +> 数据来源:wsdm2027 studio 的 venue 深度调研报告(Claude agent 从你给的 +> WSDM 官网 URL 开始爬取),存于 `.RUD/wsdm2027/ar.json` 的 `venue_report`。 +> +> **可信度说明**:WSDM 2026(第 19 届,Boise, Idaho,2026 年 2 月,录取率约 +> 16%)**没有公开单独的 oral 名单**。官方可确认的只有 Best Paper 和 +> Runner-Up 两篇;下文"代表性论文"是该届被广泛引用/讨论的 accepted +> papers,oral 身份未经证实。投稿数在不同来源间有出入(799 vs 613)。 + +## 一、两篇获奖论文(官方认证) + +### Best Paper(唯一):Diversification as Risk Minimization + +- 早稻田大学 Rikiya Takehi(本科生一作)等,arXiv 2510.22681。 +- 用人话说:搜索结果多样化(diversification)研究了二十年,大家默认它能 + "照顾到小众意图"。这篇 paper 实测发现:**经典多样化算法对小众意图的保护 + 并不比不做多样化更好**——平均指标在涨,最差情况的用户体验没人管。 +- 他们提出 VRisk(衡量"最差意图"风险的指标)和 VRisker(带近似保证的贪心 + 重排器),把最差情况的失败率降低最多 33%,代价只是平均性能掉 ~2%。 + +### Best Paper Runner-Up:TemporalExpertNet + +- 天津大学 + 快手工业数据,ACM DOI 10.1145/3773966.3777956。 +- 用人话说:电商大促(618、黑五)期间用户转化行为会突变,常规 CVR 模型 + 在大促时失灵。这篇把模型拆成"稳定编码器 + 大促敏感专家"两部分,让平时 + 学到的知识在大促期间**跨时间复用**,而不是每次大促都从头学。 + +## 二、该届代表性论文(oral 身份未证实) + +| 论文 | 主题 | +| --- | --- | +| MMQ: Multimodal Mixture-of-Quantization | Semantic ID / 生成式推荐的物品 token 化 | +| Unlocking Scaling Law in Industrial RecSys (Alibaba, 7B) | 推荐系统的 scaling law,已部署 A/B | +| OneLoc (Kuaishou, ~21% GMV 提升) | 地理感知生成式推荐,已部署 | +| TableMind | SFT+RL 训练的表格推理工具智能体 | +| Dual Conditional Diffusion Models | 扩散模型做序列推荐(该届最大建模潮流,≥7 篇) | +| How Do LLM-Generated Texts Impact Term-Based Retrieval? | LLM 生成内容对检索器的偏置(发现词法模型无偏,神经检索器有偏) | +| Multi-view Graph Condensation via Tensor Decomposition | 图压缩 / GNN 训练效率 | + +## 三、共通点(核心结论) + +1. **"一篇理论 + 一篇工业"的双主线,正是整个 program 的缩影。** + Best Paper 是有近似保证的原理性 IR 工作,Runner-Up 是快手验证的工业系 + 统。WSDM 的口味不是二选一,而是两条腿都要硬。 + +2. **质疑"平均指标",关心最差情况。** Best Paper 的整个立论就是"社区优化 + 了二十年平均值,小众意图在静默失败"。这种 **审视既有共识/评测方式** + 的角度是该届最受奖励的姿态(LLM 判官可靠性、LLM 生成内容偏置这些自反 + 性主题同理)。 + +3. **简单方法 + 可证明保证,胜过复杂堆料。** VRisker 只是一个贪心重排器, + 但带近似保证;获奖靠的是问题定义的新颖和理论的干净,不是模型的大。 + +4. **真实部署与 A/B 证据是硬通货。** 快手(两篇)、阿里(7B 大用户模型)、 + Spotify(播客冷启动)都带线上数据。纯离线 benchmark 的工作在这届明显 + 弱势。 + +5. **分布偏移 / 时间维度是共同的敌人。** 大促偏移(Runner-Up)、冷启动 + (约 8 篇)、时间上的知识复用——"世界会变,模型怎么办"是贯穿获奖和热点 + 的底层问题。 + +6. **自反性主题崛起:一边用 LLM,一边审计 LLM。** LLM 生成文本污染检索 + 语料怎么办?LLM 相关性判官能不能替代人?这些"用 AI 研究 AI 带来的问 + 题"是该届新出现的成规模主题,且 keynotes(个性化是否只会强化习惯、 + 情绪操纵)也在同一方向上敲警钟。 + +一句话版本:**WSDM 2026 奖励的是"用干净的理论工具,去戳一个大家习以为常 +的假设,并且最好带真实系统的证据"。** + +## 四、为什么 swarm 生成的 idea 长那个样子 + +评委面板是拿着上面这份报告给 206 个候选打分的,所以最终 top 20 几乎全是 +"审计/复核/戳假设"式的标题——这正是该届 venue 的口味,但标题确实不说人话。 +翻译几个高分的: + +- **swarm-01 "Structure or Semantics?"**:Semantic ID 让生成式推荐变好, + 到底是因为它编码了语义,还是只是给了模型更好用的结构先验?拆开验证。 + (对应共通点 2 + 该届最热的 Semantic ID 主题) +- **swarm-02 "Is Source Bias Mismeasured?"**:大家说神经检索器偏爱 LLM + 生成文本,但这些 benchmark 的标签本身是怎么迁移的?审计测量方法。 + (对应共通点 6) +- **swarm-05 "Risk-Sensitive Diversification Without an Oracle"**:Best + Paper 的 VRisk 假设意图分布已知,真实系统里意图是估计出来的——估计误差 + 会不会把 worst-case 保证吃掉?(直接接着 Best Paper 的开放问题做) + +如果你想要更"正向建方法"而不是"审计别人"的 idea,可以调整生成器的 persona +配比重跑一轮 swarm,或者在报告的 gaps(个性化突破信息茧房、System-1/2 统 +一助手、情绪动态建模)里挑方向定向生成。 diff --git a/docs/notes/zhizhou/WSDM2026_TOPICS_VS_MY_STACK.md b/docs/notes/zhizhou/WSDM2026_TOPICS_VS_MY_STACK.md new file mode 100644 index 00000000..b8b8d41f --- /dev/null +++ b/docs/notes/zhizhou/WSDM2026_TOPICS_VS_MY_STACK.md @@ -0,0 +1,185 @@ +# WSDM 2026 火爆 Topic × 我的技术栈对照表 + +> 每个 topic 三段:**火爆证据**(该届实际发生了什么)、**我已有的** +> (哪些论文/技能直接对得上)、**要新学的**(写出这篇 paper 还缺什么)。 +> 末尾有推荐优先级。studio 里的 `fit-XX` 卡片编号标在各 topic 后面。 + +--- + +## Topic 1 · LLM 判官与 LLM 生成内容冲击 IR 评测(自反性主题) +对应卡片:fit-01 / fit-14 / fit-20 + +**火爆证据**:该届新出现的成规模主题。TRUE 框架(LLM 做相关性标注的 +可复现性)、threshold priming 效应、"LLM 生成文本对词法检索器无偏、 +对神经检索器有偏"等多篇;keynote 也在敲 LLM 污染评测生态的警钟。 + +**我已有的** +- "No Hidden Prompts! 改排版就能骗 AI 审稿人"——攻击 LLM 判官的完整 + 方法论,换个靶子(IR 相关性判官)几乎原样能用。 +- ICML25 desk-rejection 公平性分析——学术评审生态的数学建模经验。 +- LLM 文化偏差审计(urban perception 两篇)——大规模审计实验的操作经验。 + +**要新学的** +- IR 评测传统:TREC / Cranfield 范式、qrels 是怎么造出来的、 + 评测者间一致性统计(Cohen's kappa 一类)。约 1-2 周文献量。 +- 数据污染检测:n-gram 重叠、成员推断(membership inference)。 +- (做防御篇才需要)鲁棒统计聚合:trimmed mean / median-of-means + 在排序聚合上的版本、breakdown point 理论。 + +**判断**:你所有选项里不对称优势最大、上手最快的 topic。域知识薄、 +方法论你已经发过 paper。 + +--- + +## Topic 2 · 扩散模型做推荐(该届最大建模浪潮) +对应卡片:fit-02 / fit-05 / fit-07 / fit-12 / fit-19 + +**火爆证据**:≥7 篇 accepted full papers,覆盖序列推荐、下一篮预测、 +冷启动、知识感知推荐——accepted list 上最显眼的单一建模趋势。 + +**我已有的** +- 扩散/流的方法工具箱:high-order matching、one-step shortcut 蒸馏、 + NRFlow 噪声鲁棒、force matching、HOFAR。 +- 扩散理论:GMM 视角的 smoothness 分析(ICCV25)。 +- 条件控制:TokenCompose token 级监督、OmniControlNet 双阶段条件。 +- 这是你方法+理论双主场,迁移只是换数据域。 + +**要新学的** +- 序列推荐的标准实验体系:SASRec / BERT4Rec 基线,Amazon / + MovieLens / KuaiRand 数据集,leave-one-out 评测协议, + NDCG@K / Recall@K。约 1-2 周可上手。 +- 隐式反馈的坑:负采样策略、位置偏置、曝光偏置(社区有一套约定俗成 + 的争议和陷阱,踩错评测协议会被拒)。 +- 离散空间怎么扩散:item 是离散的,社区有 embedding 空间扩散 vs + 离散扩散两条路线,要读透已有 7 篇的选择。 + +**判断**:性价比第二高。浪最大(审稿人多、关注度高),你带着别人 +没有的蒸馏/鲁棒/理论工具进场。 + +--- + +## Topic 3 · 生成式推荐 / Semantic ID / Scaling Law(工业集群) +对应卡片:fit-03 / fit-04 / fit-09 / fit-15 + +**火爆证据**:阿里 7B 大用户模型(scaling law + 线上 A/B)、快手 +OneLoc(geo 生成推荐,21% GMV)、MMQ(多模态量化 tokenization)、 +CAT-ID² 等一串工业 paper。 + +**我已有的** +- AR/VAR/FlowAR 的表达力与细粒度复杂度分析(两篇)——直接对准 + "semantic ID 自回归解码"这个新架构做理论。 +- looped MLP 可编程性——做"模型规模必要性"下界。 +- HSR 稀疏注意力、近线性梯度近似——长序列用户模型的效率切口。 + +**要新学的** +- 生成式检索文献线:DSI → NCI → TIGER → MMQ 的演进和各自的坑。 +- Semantic ID 怎么造:RQ-VAE / 残差量化 / 多模态量化。 +- 工业推荐架构常识:召回-排序两段式、embedding 表、终身行为序列 + 建模(SIM/TWIN)。不需要真实工业数据也能做理论+公开数据验证, + 但叙事要懂行。 + +**判断**:理论切口是你的护城河(这个社区缺会证下界的人),但要花 +2-3 周啃工业文献才能讲对话。 + +--- + +## Topic 4 · LLM Agent + 工具使用 + RL +对应卡片:fit-06 / fit-10 / fit-17 + +**火爆证据**:TableMind(SFT+RL 表格推理 agent)、TOOL-CURE(课程 +RL 选工具)、CoDA(层次 RL agent)、LLM agent 刷分攻击推荐系统; +Industry Day keynote 全在讲 agentic。 + +**我已有的** +- RLVR 理论与实践:off-principals 分析(43 引用)、ISO 优化栈。 +- 多智能体:MEMO 记忆增强多轮博弈。 +- RL 训练的直觉和踩坑经验是现成的。 + +**要新学的** +- Agentic search/RAG 训练管线:Search-R1 一类的环境搭建、 + rollout 基建(这块工程量不小)。 +- 工具调用的数据构造与评测基准(HotpotQA、多跳 QA、工具链任务)。 +- 推荐系统安全文献(若走刷分攻防线:shilling attack 的经典设定)。 + +**判断**:能力对口,但工程基建成本是四个高优 topic 里最重的, +适合愿意搭环境的时候做。 + +--- + +## Topic 5 · 受限图学习:压缩 / 遗忘 / 对抗鲁棒 +对应卡片:fit-08 / fit-18 + +**火爆证据**:GCTD(张量分解图压缩)、离散域多面压缩、GNN 遗忘 +反演攻击、Forget-and-Explain 遗忘验证——一整簇。 + +**我已有的** +- DP-NTK(WACV25)——接"可认证遗忘"正合适(遗忘 ≈ 隐私的孪生问题)。 +- DPBloomFilter——隐私数据结构。 +- rank-1 矩阵感知样本复杂度——接"图压缩能压到多小"的下界问题。 + +**要新学的** +- **GNN 基础全套**:GCN / GAT / LightGCN、消息传递框架、图上的 + 评测协议。你的发表列表里没有图学习工作,这是真正要补的课 + (约 3-4 周)。 +- 图压缩方法线和遗忘的定义谱系(exact / approximate / certified)。 + +**判断**:理论接口漂亮,但 GNN 是从零学。适合作为第二梯队。 + +--- + +## Topic 6 · 可信 LLM:RAG 鲁棒、事实核查、引用归因 + +**火爆证据**:KnowFC / DagFC(知识冲突下的事实核查)、C²-Cite +(引用归因)、检索增强生成的抽取-生成对齐。 + +**我已有的** +- 审计方法论和理论功底可以泛化,但**没有直接对口的论文**——这是 + 六个热点里你接口最薄的。 + +**要新学的** +- RAG 全栈(检索器 + 生成器 + 知识冲突处理)、FEVER 线的事实核查 + 数据集、归因评测协议。基本等于进一个新领域。 + +**判断**:除非有特别想做的角度,否则不推荐从这里进。 + +--- + +## Topic 7 · 冷启动与工业转化建模(CVR) + +**火爆证据**:约 8 篇冷启动(bundle / app / podcast / 序列), +Best Paper Runner-Up(TemporalExpertNet,大促 CVR)也在这条线, +Spotify 案例研究——工业气息最重的主题。 + +**我已有的** +- RichSpace 的 embedding 插值思路可做冷启动数据增广;理论功底可 + 做 delayed feedback 建模。接口偏弱。 + +**要新学的** +- CTR/CVR 建模全套:特征工程、多任务学习、延迟转化、在线学习; + 外加大促业务理解。没有工业数据和线上 A/B,这个主题很难写出该届 + 获奖那种说服力。 + +**判断**:不推荐。你的比较优势在理论和生成模型,不在工业经验。 + +--- + +## Keynote 开放方向(可做故事加成,不建议单独立项) + +- **Worst-case 而非平均**(Best Paper 的精神):任何 topic 里加一层 + "最差情况分析"都会讨喜——你的理论功底正好干这个(fit-13/16 用了)。 +- 信息茧房突破(fit-19)、System-1/2 统一助手、情绪动态——故事好听, + 单独做风险高,适合当某个 idea 的动机段。 + +--- + +## 推荐优先级(综合"你的接口厚度 × 浪的大小 × 新学成本") + +| 优先级 | Topic | 接口厚度 | 新学成本 | 一句话 | +| --- | --- | --- | --- | --- | +| 1 | LLM 判官 / 评测审计 | 极厚(原样迁移) | 低(1-2 周) | 不对称优势最大 | +| 2 | 扩散推荐 | 厚(方法+理论) | 低(1-2 周) | 最大的浪,带工具进场 | +| 3 | Semantic ID / 生成式推荐 | 厚(理论切口) | 中(2-3 周) | 缺理论的社区,你是稀缺供给 | +| 4 | LLM Agent + RL | 中 | 高(基建重) | 对口但费工程 | +| 5 | 图压缩 / 遗忘 | 中(DP+复杂度) | 高(GNN 从零) | 第二梯队 | +| 6 | 可信 RAG | 薄 | 很高 | 不推荐进场 | +| 7 | 冷启动 / CVR | 薄 | 很高(要工业数据) | 不推荐 | diff --git a/docs/notes/zhizhou/WSDM2027_PERSONAL_IDEAS.md b/docs/notes/zhizhou/WSDM2027_PERSONAL_IDEAS.md new file mode 100644 index 00000000..8f0baaad --- /dev/null +++ b/docs/notes/zhizhou/WSDM2027_PERSONAL_IDEAS.md @@ -0,0 +1,228 @@ +# WSDM 2027 个人定制 Idea 池(20 个) + +> 生成逻辑:你的发表记录(`my-google-scholar.pdf`)× WSDM 2026 热点 +> (`WSDM2026_BEST_ORAL_SUMMARY.md`)。每个 idea 标注了三件事: +> **你的接口**(你哪篇工作/哪项技能接得上)、**要新学的**(这个 idea +> 里对你是新东西的部分)、**WSDM 对接点**。 +> 排序按"桥的牢固程度"(fit 分)降序,和 studio 里的卡片一一对应 +> (`fit-01` ~ `fit-20`)。 +> +> 你的能力圈速写:① 扩散/流生成模型的方法与理论(TokenCompose、 +> smoothness/GMM、high-order matching、NRFlow);② AR/VAR/FlowAR 的 +> 表达力与细粒度复杂度(VAR limits、FlowAR、looped MLP、HSR 稀疏注意 +> 力、近线性梯度);③ 差分隐私(DP-NTK、DPBloomFilter);④ RLVR 与 +> 多智能体(off-principals、ISO、MEMO);⑤ 评审生态审计(ICML25 +> desk-rejection 公平性、"No Hidden Prompts" 攻击 AI 评审);⑥ LLM +> 城市/文化偏差(UrbanAlign、culturally uneven perception)。 + +--- + +## 直接迁移带(你的方法 → 推荐/检索的新战场) + +### fit-01 · Presentation-Only Gaming of LLM Relevance Judges(0.95) +- **人话**:你证明过"只改排版不改内容就能骗过 AI 审稿人"。IR 社区正在 + 大规模用 LLM 当相关性判官(标注 qrels)。同样的攻击在这里成立吗—— + 一篇网页只靠改格式、加小标题、换措辞,能不能骗 LLM 判官给出更高相关 + 性?如果能,这就是"针对 LLM 判官的 SEO",整个评测生态都有问题。 +- **你的接口**:arXiv 2606.13044 的攻击方法论几乎原样可用。 +- **要新学的**:IR 评测体系(TREC qrels、Cranfield 范式)、WSDM26 的 + TRUE 框架和 priming-effect 论文。 +- **WSDM 对接点**:热点 E(LLM 判官重塑 IR 评测),该届自反性主题的 + 正中心。 + +### fit-02 · One-Step Diffusion Recommenders via High-Order Shortcut Distillation(0.92) +- **人话**:WSDM26 最大的建模潮流是扩散推荐(≥7 篇),但都要几十步采 + 样,线上根本部署不起。你做过 one-step shortcut diffusion 的高阶匹配 + 蒸馏——把它搬过来,把扩散推荐器蒸成一步出结果,延迟直接对齐工业上线 + 标准。 +- **你的接口**:High-order matching for one-step shortcut diffusion + (2502.00688)、HOFAR。 +- **要新学的**:序列推荐的标准 setup(SASRec/BERT4Rec 基线、 + Amazon/MovieLens 协议)、线上延迟预算怎么算。 +- **WSDM 对接点**:热点 A(扩散推荐)+ 工业口味(部署可行性)。 + +### fit-03 · Computational Limits of Generative Retrieval over Semantic IDs(0.90) +- **人话**:生成式检索/推荐把物品编成 semantic ID 序列,然后自回归解 + 码。你对 VAR/FlowAR 做过的"表达力 + 细粒度复杂度"分析,在这个新架构 + 上没人做过:什么条件下生成式检索能被证明匹配稠密检索?beam search + 在 ID 树上的复杂度下界是什么? +- **你的接口**:VAR computational limits (2501.04377)、FlowAR + expressivity (AISTATS26)。 +- **要新学的**:生成式检索这条线(DSI、NCI、TIGER、MMQ)。 +- **WSDM 对接点**:热点 B(semantic ID / 生成式推荐)。 + +### fit-04 · Provable Sparse Attention for Lifelong User-Behavior Sequences(0.88) +- **人话**:工业推荐要在十万级的用户终身行为序列上做注意力,现在的做 + 法(先检索再注意力,如 SIM/TWIN)没有理论保证、可能漏掉关键历史。 + 你的 HSR 稀疏注意力加速自带"可证明不漏"的结构——搬到终身序列建模, + 给出第一个带 recall 保证的长序列用户模型。 +- **你的接口**:HSR-enhanced sparse attention(CPAL25)。 +- **要新学的**:终身序列建模的工业方案与数据集。 +- **WSDM 对接点**:热点 B(大用户模型)+ 工业口味。 + +### fit-05 · Noise-Robust Diffusion Recommendation for Implicit Feedback(0.86) +- **人话**:推荐的训练信号是隐式反馈(点击),里面全是噪声:误点、位 + 置偏置、从众。你做过噪声鲁棒的生成建模(NRFlow 的高阶机制),把"标 + 签噪声下的扩散训练"搬到交互噪声下的扩散推荐,直接回答"扩散推荐器对 + 脏数据到底稳不稳"。 +- **你的接口**:NRFlow(UAI25)、force matching(CIKM25)。 +- **要新学的**:隐式反馈去偏那套文献(position bias、exposure bias)。 +- **WSDM 对接点**:热点 A + 工业数据现实。 + +### fit-06 · Do RL-Trained Search Agents Learn Off the Principals?(0.85) +- **人话**:现在流行用 RL 训练会搜索的 LLM agent(Search-R1 一类)。 + 你的 RLVR 理论说:RL 更新其实避开了主参数方向、走的是"偏离主成分" + 的路径。把这个分析工具对准搜索 agent:它们学到的是"怎么搜"的通用能 + 力,还是过拟合了训练用的那个检索器?换个检索器还行不行? +- **你的接口**:RLVR off-principals (2511.08567)、ISO (2607.19331)。 +- **要新学的**:agentic search/RAG 的训练管线和评测。 +- **WSDM 对接点**:热点 C(RL 训练的 LLM agent)。 + +### fit-07 · A Multimodality Criterion for When Diffusion Beats AR in Recommendation(0.84) +- **人话**:什么时候值得用扩散做推荐、什么时候自回归就够了?你的 + ICCV25 论文用高斯混合视角刻画了扩散模型的平滑性。用户偏好分布天然 + 是多峰混合(一个人同时喜欢好几类东西)——把你的分析搬过来,给出一个 + 可检验的判据:后验多峰性强到什么程度,扩散才开始赢。 +- **你的接口**:Smoothness of diffusion via Gaussian mixture(ICCV25)。 +- **要新学的**:推荐里的多兴趣建模文献(multi-interest retrieval)。 +- **WSDM 对接点**:热点 A 的"何时该用"元问题,评委最爱的戳假设角度。 + +### fit-08 · Certified Machine Unlearning for Recommenders via NTK Regression(0.82) +- **人话**:用户行使"被遗忘权"后,推荐模型真的忘了他吗?WSDM26 有一 + 簇图遗忘/遗忘验证的 paper,但基本是启发式。你的 DP-NTK 工作正好提供 + 了带证书的工具:在 NTK 回归视角下做可认证的推荐模型遗忘,给出"忘没 + 忘"的数学保证而不是经验检查。 +- **你的接口**:DP mechanisms in NTK regression(WACV25)。 +- **要新学的**:unlearning 的定义谱系和图遗忘验证(该届的 + Forget-and-Explain 等)。 +- **WSDM 对接点**:热点 F(图遗忘/可信)。 + +--- + +## 半迁移带(你带一半工具,另一半要新学) + +### fit-09 · Expressivity Lower Bounds: What User-Model Scale Is Actually Necessary?(0.80) +- **人话**:阿里的 7B 大用户模型宣称推荐有 scaling law。但没人问下界: + 协同过滤这个任务本身需要多大的模型才能表达?你的 looped-MLP"可编程 + 计算机"和表达力分析可以构造:什么规模以下必然表达不了某类用户-物品 + 结构。给 scaling 狂热泼一盆有定理的冷水。 +- **你的接口**:Looped ReLU MLPs(AISTATS25)、复杂度分析全家桶。 +- **要新学的**:推荐 scaling law 的实证结果与协同过滤的谱结构。 +- **WSDM 对接点**:热点 B(scaling law),戳假设角度。 + +### fit-10 · Multi-Agent LLM Shilling: Coordinated Attacks and Provable Detection(0.78) +- **人话**:WSDM26 已有"LLM agent 刷分攻击推荐系统"的 paper,但都是单 + agent。你做过多智能体记忆协作(MEMO):一群带记忆、会协调的 LLM + agent 能把刷分攻击做到多隐蔽?反过来,协调性本身是不是可检测的指纹? + 攻防两端都做。 +- **你的接口**:MEMO (2603.09022)、多智能体博弈经验。 +- **要新学的**:推荐系统安全/托攻击(shilling)的经典文献。 +- **WSDM 对接点**:热点 C(agent)× 可信推荐,该届已有先例文章。 +- **注意**:安全攻防题,写作要走"红队为了防御"的框架。 + +### fit-11 · Differentially Private Sketches for Streaming Recommendation Infrastructure(0.76) +- **人话**:工业推荐的底层全是流式频率结构:频控(frequency capping)、 + 去重、热门统计,而这些 sketch 会泄露用户行为。你做过 DPBloomFilter, + 往前推一步:一整套带 DP 保证的流式 sketch(Bloom/CountMin/HLL)用于 + 推荐基础设施,量化"隐私预算 vs 推荐质量"的真实代价。 +- **你的接口**:DPBloomFilter (2502.00693)。 +- **要新学的**:工业推荐的流式架构(谁在什么环节用什么 sketch)。 +- **WSDM 对接点**:工业口味 + 可信;WSDM 一直收系统向 paper。 + +### fit-12 · Controllable Diffusion Recommendation with Token-Level Supervision(0.75) +- **人话**:TokenCompose 用 token 级监督让文生图听话;ControlNet 加条 + 件控制。推荐这边的对应问题是"可控推荐":运营要保量、多样性要保底、 + 类目要平衡。把条件控制机制搬进扩散推荐器,让一个模型在推理时接受可 + 调的控制信号,而不是训练 N 个模型。 +- **你的接口**:TokenCompose(CVPR24)、OmniControlNet(CVPR24)。 +- **要新学的**:可控推荐/约束重排的业务设定。 +- **WSDM 对接点**:热点 A + 工业可用性。 + +### fit-13 · Exposure Fairness Under Submission Constraints: A Mechanism-Design View of Ranking(0.74) +- **人话**:你在 ICML25 用数学分析过"投稿限额政策对谁不公平"。同一套 + 机制设计+公平性数学,换个对象:平台的曝光分配政策(限流、频控、创作 + 者配额)对小创作者是否系统性不公平?给出可证明的机制设计改进。这也 + 接上了 Best Paper 的 worst-case 精神——平均曝光在涨,尾部创作者在死。 +- **你的接口**:Desk-rejection fairness(ICML25)的分析框架。 +- **要新学的**:创作者经济/曝光公平的文献(two-sided marketplace)。 +- **WSDM 对接点**:Best Paper 的风险敏感精神 × web 平台机制。 + +### fit-14 · Manipulation-Resistant LLM Judge Panels with Provable Breakdown Points(0.72) +- **人话**:fit-01 证明单个 LLM 判官可被排版攻击。防御端:怎么组一个 + 判官面板(多模型+聚合规则),使得"被操纵的判官不超过 k 个时,最终判 + 决可证明不变"?借鉴鲁棒统计的 breakdown point,把你攻击论文的对抗视 + 角转成防御设计。 +- **你的接口**:"No Hidden Prompts" 的攻击模型 + 理论功底。 +- **要新学的**:鲁棒统计聚合(trimmed mean、median-of-means 在排序上 + 的版本)。 +- **WSDM 对接点**:热点 E,攻防成对投稿的防御篇。 + +### fit-15 · Almost-Linear-Time Training for Billion-Parameter User Models(0.70) +- **人话**:推荐大模型(7B 用户模型)的训练成本是工业最痛的账单。你证 + 明过多层 transformer 梯度可以近线性时间近似。把这个理论结果落到用户 + 模型训练上做系统实现:什么近似精度下 A/B 指标不掉?第一个把"近似梯 + 度理论"带进推荐训练的工作。 +- **你的接口**:Almost-linear gradient (2408.13233)、async SGD。 +- **要新学的**:推荐训练 infra(embedding 表、流式训练)的工程现实。 +- **WSDM 对接点**:热点 B(scaling)× 工业效率。 + +--- + +## 探索带(你只带入场券,主体是新领域——想学新东西选这几个) + +### fit-16 · Worst-Case Regional Fairness Auditing of Geo-Aware Recommenders(0.68) +- **人话**:快手 OneLoc 用地理感知生成推荐拿了 21% GMV,但没人问:小 + 城市/少数族裔社区拿到的推荐质量是不是系统性更差?你有"LLM 城市感知 + 的文化不均"审计经验 + Best Paper 的 VRisk 最差情况度量——合起来做地 + 理维度的 worst-case 公平审计。 +- **你的接口**:Culturally uneven urban perception (2604.20048)、 + UrbanAlign。 +- **要新学的**:本地生活推荐的业务与数据。 +- **WSDM 对接点**:Best Paper 的 worst-case 精神 × 热点 B 的地理生成 + 推荐 × keynote 的社会关切。 + +### fit-17 · RLVR for Tool-Use Retrieval Agents: Verifiable Rewards from Retrieval Outcomes(0.66) +- **人话**:RLVR 火是因为奖励可验证(对/错)。检索恰好天然可验证:文 + 档里有没有答案、引用对不对。把你的 ISO 优化栈的经验搬到"检索工具使 + 用 agent"的 RL 训练上,设计一套以检索结果为可验证奖励的训练配方,对 + 比 TOOL-CURE 那类课程学习。 +- **你的接口**:ISO RLVR stack、RLVR 训练直觉。 +- **要新学的**:工具调用 agent 的数据构造与评测(这块对你基本全新)。 +- **WSDM 对接点**:热点 C 正中心。 + +### fit-18 · Sample-Complexity Limits of Graph Condensation for Recommendation(0.64) +- **人话**:图压缩(把大图缩成小图训练)在 WSDM26 是一簇热点,但全是 + "怎么压",没人回答"最多能压到多小"。你的 rank-1 矩阵感知样本复杂度 + 技术正好是这个问题的工具:保住协同过滤谱结构所需的最小交互数是多少? + 给这个热门方向立一块理论界碑。 +- **你的接口**:Rank-1 matrix sensing 样本复杂度。 +- **要新学的**:图压缩方法这条线(GCTD、离散域压缩)。 +- **WSDM 对接点**:热点 F,为方法潮流补理论下界(评委最吃这套)。 + +### fit-19 · Serendipity by Diffusion: Escaping Filter Bubbles with Controlled Noise(0.62) +- **人话**:Caverlee 的 keynote 问"个性化能不能带来真正的新发现而不是 + 强化旧习惯"。扩散模型天然有一个被忽视的旋钮:注入噪声的幅度控制探 + 索半径。做一个"意外但连贯"的推荐生成器——用噪声调度控制新颖度,用 + worst-case 意图覆盖评估(接 VRisk),回答信息茧房这个 keynote 级问题。 +- **你的接口**:扩散模型的噪声机制理解。 +- **要新学的**:serendipity/diversity 的评测传统(这块很成熟,坑也多)。 +- **WSDM 对接点**:keynote gap × 热点 A,故事性最强的一个。 + +### fit-20 · Knowledge-Cutoff Contamination in LLM Relevance Judgments(0.60) +- **人话**:LLM 判官的训练语料可能见过评测集的文档和查询——它给的"相 + 关性判断"到底是判断还是背诵?设计截断日期前后的对照实验,量化污染对 + qrels 质量的影响。这是 swarm-10 的题,但从你的"评审生态审计"视角切 + 入你完全能驾驭,且和 fit-01 共享实验基建。 +- **你的接口**:评测审计的方法论嗅觉(ICML25 + 攻击 AI 评审)。 +- **要新学的**:数据污染检测技术(n-gram 重叠、成员推断)。 +- **WSDM 对接点**:热点 E,和 fit-01/fit-14 构成一个投稿集群。 + +--- + +## 怎么用 + +- 20 张卡已写入 wsdm2027 studio(`fit-01` ~ `fit-20`,分数即上面的 + fit 值),原 swarm 池备份在 `.RUD/wsdm2027/swarm-pool.json`。 +- 建议的组团方式:**评测审计团**(fit-01/14/20,共享基建,你最有不对 + 称优势)、**扩散推荐团**(fit-02/05/07/12/19,蹭最大浪)、**理论界 + 碑团**(fit-03/09/18,你最难被抄袭的护城河)。 diff --git a/docs/notes/zhizhou/my-google-scholar.pdf b/docs/notes/zhizhou/my-google-scholar.pdf new file mode 100644 index 00000000..4811e610 Binary files /dev/null and b/docs/notes/zhizhou/my-google-scholar.pdf differ diff --git a/loom/ar_task.py b/loom/ar_task.py index 1927abc4..a9751345 100644 --- a/loom/ar_task.py +++ b/loom/ar_task.py @@ -314,6 +314,19 @@ def is_ar_kind(kind: str | None) -> bool: "page_limit": 9, "invitation": "colmweb.org/COLM/{year}/Conference/-/Submission", }, + { + "id": "wacv", + "label": "WACV", + "template": "wacv", + "aliases": [ + "WACV", + "IEEE/CVF Winter Conference on Applications of Computer Vision", + ], + "page_limit": 8, + # WACV uses CMT rather than OpenReview; keep this empty so Loom does + # not fabricate an invitation id. + "invitation": "", + }, ) VENUE_IDS = frozenset(v["id"] for v in VENUES) @@ -563,6 +576,8 @@ def new_studio_state( venue: str = DEFAULT_VENUE, mode: str = MODE_AUTO, seed_idea: str = "", + venue_url: str = "", + venue_kickoff: bool = False, max_rounds: Any = DEFAULT_MAX_ROUNDS, ) -> dict[str, Any]: d = (direction or "").strip().lower() @@ -597,6 +612,8 @@ def new_studio_state( "search_suggest_status": "idle", "search_suggest_error": "", "venue_report": {}, + "venue_url": venue_url.strip(), + "venue_kickoff": bool(venue_kickoff), "venue_status": "idle", "venue_error": "", "venue_updated_at": "", @@ -950,6 +967,7 @@ def review_note_path(project_root: Path, slug: str, n: int) -> Path: TOKEN_TITLE = "@@TITLE@@" TOKEN_RUNNING_TITLE = "@@RUNNING_TITLE@@" TOKEN_KEYWORDS = "@@KEYWORDS@@" +TOKEN_WACV_TRACK = "@@WACV_TRACK@@" def templates_paper_dir() -> Path: @@ -1029,8 +1047,19 @@ def seed_paper_skeleton( return False, f"failed to seed skeleton: {exc}" title = str((idea or {}).get("title") or "").strip() or "Untitled AR Submission" + # Personalized Studio cards are bilingual ("中文 — English"). The UI keeps + # both halves, but pdfLaTeX venue templates cannot typeset CJK safely + # without adding a different font stack. Seed the manuscript with the + # English publication title while preserving the bilingual title in state. + if " — " in title: + english_title = title.rsplit(" — ", 1)[-1].strip() + if english_title: + title = english_title keywords = str((idea or {}).get("metric") or "").strip() or "machine learning" running = title if len(title) <= 60 else title[:57].rstrip() + "..." + wacv_track = str((idea or {}).get("wacv_track") or "algorithms").strip().lower() + if wacv_track not in {"algorithms", "applications", "datasets"}: + wacv_track = "algorithms" main = dest / "main.tex" try: text = main.read_text(encoding="utf-8") @@ -1038,6 +1067,7 @@ def seed_paper_skeleton( text.replace(TOKEN_TITLE, _tex_escape(title)) .replace(TOKEN_RUNNING_TITLE, _tex_escape(running)) .replace(TOKEN_KEYWORDS, _tex_escape(keywords)) + .replace(TOKEN_WACV_TRACK, wacv_track) ) main.write_text(text, encoding="utf-8") except OSError as exc: @@ -1758,13 +1788,30 @@ def research_venue_cycle( web search: award pages, accepted-paper lists, and trend write-ups. The reply is normalized and bounded before it is trusted. """ - venue = str(venue_entry(str(state.get("venue") or DEFAULT_VENUE)).get("label")) direction = direction_label(state) + venue_url = str(state.get("venue_url") or "").strip() + if venue_url: + # The operator's URL names the venue. The catalog dropdown only picks + # a paper TEMPLATE and must never override which venue gets surveyed + # (a WSDM URL once lost to the dropdown's default ICLR). + venue = f"the venue that owns {venue_url}" + start_block = ( + f"START HERE: the operator supplied this venue page - {venue_url}\n" + "That page decides which venue you survey; identify the venue from\n" + "the page itself and say its name in the `cycle` field. Crawl it and\n" + "the pages it links (awards, accepted papers, program) before\n" + "falling back to your own web search for anything missing.\n\n" + ) + else: + venue = str( + venue_entry(str(state.get("venue") or DEFAULT_VENUE)).get("label") + ) + start_block = "Use your own web search. " prompt = f"""You are surveying the most recent COMPLETED cycle of {venue} so a research studio can propose ideas that fit what this venue actually rewards. The studio's research direction is: {direction}. -Use your own web search. For the last completed edition of {venue}, find: +{start_block}For the last completed edition of {venue}, find: 1. the best paper / honorable mention winners; 2. papers highlighted as orals or award candidates (up to 12); 3. the hottest topics of that cycle - recurring themes across accepted papers, @@ -2536,7 +2583,12 @@ def run_reviewer( f"reviewing compiled PDF with Cursor panel: {', '.join(selected)}" ) - with TemporaryDirectory(prefix="loom-ar-pdf-review-") as tmp: + # Cursor can leave short-lived files behind while an NFS-backed temporary + # directory is being removed. The review result is already in memory, so a + # cleanup race must never wedge the AR driver after every reviewer returned. + with TemporaryDirectory( + prefix="loom-ar-pdf-review-", ignore_cleanup_errors=True + ) as tmp: workspace = Path(tmp) review_pdf = workspace / "submission.pdf" try: diff --git a/loom/skills/ar/GPU-RESOURCES.md b/loom/skills/ar/GPU-RESOURCES.md index a17035fe..22a898e1 100644 --- a/loom/skills/ar/GPU-RESOURCES.md +++ b/loom/skills/ar/GPU-RESOURCES.md @@ -1,6 +1,6 @@ -# Compute resources — run every experiment on the GPU cluster +# Compute resources — run every experiment on a GPU compute node -The machine your pane runs on is a **slurm login node with NO GPU** and only 32 +The machine your pane runs on is a **login node with NO GPU** and only ~32 oversubscribed CPU cores. Model inference on it takes minutes per item where an H100 takes seconds. Running experiments locally is the single biggest cause of slow author rounds — a round that should take under an hour stretches to 8–19 @@ -8,58 +8,76 @@ hours on local CPU. **Rule: never run model inference or training on the login node.** Small aggregation/plotting scripts are fine locally; anything that loads model -weights goes to the cluster. +weights goes to a GPU compute node. -## Slurm (preferred) +## Do NOT use Slurm on this cluster -The `batch` partition has nodes with **8x NVIDIA H100 80GB** each (176 CPU -cores, ~1TB RAM per node). Queue wait is typically minutes. +Slurm here is unreliable: the queue backs up for hours and chained +`sbatch --dependency` jobs frequently wedge into `DependencyNeverSatisfied` and +never run, which hangs your round. **Do not use `sbatch`, `srun`, or any Slurm +command.** Ignore Slurm entirely and run directly on a compute node over SSH. -Interactive / one-off: +## Find a free GPU, then SSH to the node and run there + +A background scout refreshes a free-GPU inventory every ~60 seconds: ``` -srun --partition=batch --gres=gpu:1 --cpus-per-task=16 --mem=100G \ - --time=04:00:00 --job-name=- +/data/shared/zhizhousha/gpu-scout/free_gpus.txt # human/agent-facing table +/data/shared/zhizhousha/gpu-scout/free_gpus.json # same data, machine-readable ``` -Long or parallel lanes — write a script and submit with `sbatch` (same flags, -plus `--output=logs/%x-%j.out`), one job per lane; check with -`squeue -u $USER`. Free-GPU overview: `sinfo -p batch -O NodeList,Gres,GresUsed`. +"Free" means `nvidia-smi` memory.used < 2000 MiB on that GPU. Each node has 8× +H100 80GB (176 CPU cores, ~1 TB RAM). Workflow every time you need a GPU: + +1. **Read the inventory** (`cat /data/shared/zhizhousha/gpu-scout/free_gpus.txt`) + and pick a `node` + `gpu` index. For N parallel lanes, pick N different + `node:gpu` pairs. +2. **Re-verify right before launch** — the inventory can be up to a minute + stale and GPUs are shared, so confirm the exact GPU is still idle: + + ``` + ssh "nvidia-smi --query-gpu=index,memory.used --format=csv,noheader,nounits | awk -F', ' '\$1=={print \$2}'" + ``` + + If that prints a number ≥ ~2000, pick another `node:gpu` from the inventory. +3. **Launch on the node**, pinned to that GPU, detached, logging into your + worktree (which is on shared storage the node can see): -What carries over transparently: the shared filesystem. Your `work/` tree, the -HuggingFace cache (`research-factory/.cache/huggingface`), and your `.venv`s -are all on shared storage and visible from compute nodes — activate the same -venv inside the job. + ``` + ssh 'cd ; \ + CUDA_VISIBLE_DEVICES= setsid nohup ./.venv/bin/python train.py \ + > runs/.log 2>&1 & echo "PID $! on gpu "' + ``` +4. **Poll from your pane** by tailing the logfile (shared FS): `tail -n 40 + runs/.log`. Do **not** open a blocking wait; sleep-poll the log/output + files and move on to other work between checks. +5. **Clean up** when a lane finishes or you abandon it: `ssh "pkill -f + "` so you free the GPU for the next lane + and for other people. + +What carries over transparently: the **shared filesystem**. Your `work/` tree, +the HuggingFace cache (`research-factory/.cache/huggingface`), and your `.venv`s +are all on shared storage and visible from every compute node — activate the +same venv inside the SSH command. ## Migration guidance - **transformers-based runners**: the same script works on a GPU node with - `device_map="auto"` (or `.to("cuda")`). This is usually a one-line change. + `device_map="auto"` (or `.to("cuda")`). Usually a one-line change. - **llama.cpp / GGUF CPU servers**: on a GPU node, prefer serving the original - HF checkpoint with transformers or vLLM inside the job; GGUF quantizations - exist for CPU. If you must keep llama.cpp, use a CUDA build with `-ngl 999`. -- **Parity first**: before committing to GPU results, rerun one small batch - (greedy / temperature 0) and confirm it matches your CPU outputs; note any - numeric drift in the round summary rather than silently mixing backends. -- **Mid-experiment**: let a batch that is nearly done finish where it started; - submit all remaining chunks to the GPU. Never mix backends within one - reported table without saying so. -- **Record the recipe**: once a slurm invocation works for your codebase, write - it into your notes (README or scratch notes) so every later round reuses it - instead of rediscovering flags. - -## Defensive preflight in every GPU job - -Occasionally a leaked process squats a GPU outside slurm's accounting, and jobs -scheduled onto that GPU OOM at model load. Start every sbatch script with a -guard: query the assigned GPU's used memory (`nvidia-smi ---query-gpu=memory.used --format=csv,noheader`), and if it is already above -~20 GB, `scontrol requeue $SLURM_JOB_ID` and exit instead of loading the model. -This turns a night of OOM-failed jobs into a few cheap requeues. - -## If slurm is full - -`tscheduler` (`/data/shared/zhizhousha/workspace/loom-project/tscheduler`) can -locate free GPUs on Together's Kubernetes clusters: `scripts/radar.sh snapshot` -or `scripts/radar.sh find h100 8`. Slurm is simpler — reach for tscheduler only -when the batch partition has no capacity. + HF checkpoint with transformers or vLLM; if you must keep llama.cpp, use a + CUDA build with `-ngl 999`. +- **Parity first**: before trusting GPU results, rerun one small batch (greedy / + temperature 0) and confirm it matches your CPU outputs; note any numeric drift + in the round summary rather than silently mixing backends. +- **Record the recipe**: once an SSH launch works for your codebase, write the + exact command into your notes (README or scratch notes) so every later round + reuses it. Keep experiments modest and convergent — get real numbers for this + round first, widen the grid in later rounds. + +## Never squat a GPU + +Pin exactly one GPU per process with `CUDA_VISIBLE_DEVICES`, and kill your +process as soon as its lane is done. Do not hold GPUs idle "in reserve". The +inventory is shared with 7 sibling papers and other people — take what you use +and release it. diff --git a/loom/skills/ar/gpu-resource/README.md b/loom/skills/ar/gpu-resource/README.md new file mode 100644 index 00000000..79f105a9 --- /dev/null +++ b/loom/skills/ar/gpu-resource/README.md @@ -0,0 +1,69 @@ +# gpu-resource — free-GPU scout for direct-to-node experiments + +This cluster's Slurm scheduler is unreliable: queues back up for hours and +chained `sbatch --dependency` jobs frequently wedge into +`DependencyNeverSatisfied` and never run, which hangs AR author rounds. So AR +authors **bypass Slurm and run jobs directly on compute nodes over SSH**. The +piece that makes that safe is this scout: it continuously reports which +`node:gpu` pairs are actually idle. + +The agent-facing methodology (what an author does with the inventory) lives in +`../GPU-RESOURCES.md`, which is injected into every author prompt via +`ar_task.gpu_resources_block()`. This folder holds the **operator-side daemon** +that produces the inventory that skill tells authors to read. + +## What `gpu_scout.py` does + +Every `INTERVAL` seconds it: + +1. Lists reachable compute nodes (`sinfo`, skipping down/drain/etc.), or uses + `$GPU_SCOUT_NODES` if set. +2. SSHes each node in parallel and reads real `nvidia-smi` usage — it does **not** + trust Slurm's alloc/idle state, because a Slurm-"alloc" node often still has + idle GPUs. +3. Marks every GPU with `memory.used < THRESHOLD_MIB` (default 2000) as free. +4. Atomically republishes two files: + - `free_gpus.json` — machine-readable inventory + - `free_gpus.txt` — the table authors read (node → free gpu indices) + +## Inventory location (the contract) + +Default output dir: `/data/shared/zhizhousha/gpu-scout/` + +``` +/data/shared/zhizhousha/gpu-scout/free_gpus.txt +/data/shared/zhizhousha/gpu-scout/free_gpus.json +``` + +`../GPU-RESOURCES.md` points authors at exactly this path, so if you change +`GPU_SCOUT_OUT` you must update that skill too. + +## Running it + +```bash +# from the repo root, with the project venv +./.venv/bin/python loom/skills/ar/gpu-resource/gpu_scout.py # loop forever +./.venv/bin/python loom/skills/ar/gpu-resource/gpu_scout.py --once # one sweep +``` + +Run it once as a long-lived background daemon (one per operator/cluster) while +AR papers are training; the 8+ author panes all read the same published files. + +## Configuration (env vars) + +| var | default | meaning | +| --- | --- | --- | +| `GPU_SCOUT_OUT` | `/data/shared/zhizhousha/gpu-scout` | where to publish the inventory | +| `GPU_SCOUT_INTERVAL` | `60` | seconds between sweeps | +| `GPU_SCOUT_THRESHOLD` | `2000` | a GPU is "free" below this many MiB used | +| `GPU_SCOUT_SSH_TIMEOUT` | `12` | per-node SSH connect timeout (s) | +| `GPU_SCOUT_WORKERS` | `24` | parallel SSH probes | +| `GPU_SCOUT_NODES` | _(unset)_ | comma/space node list overriding `sinfo` | + +## Requirements + +- Passwordless SSH (BatchMode) from the login node to the compute nodes. +- `nvidia-smi` on each node; `sinfo` on the login node (only for discovery — + set `GPU_SCOUT_NODES` to skip Slurm entirely). +- The shared filesystem is visible on the compute nodes, so an author writes + outputs into its `work/` tree from the node and tails them from its pane. diff --git a/loom/skills/ar/gpu-resource/gpu_scout.py b/loom/skills/ar/gpu-resource/gpu_scout.py new file mode 100644 index 00000000..b03e7886 --- /dev/null +++ b/loom/skills/ar/gpu-resource/gpu_scout.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python3 +"""Free-GPU scout for the AR compute cluster. + +The cluster's Slurm scheduler is unreliable (queues back up for hours, chained +`sbatch --dependency` jobs wedge into ``DependencyNeverSatisfied`` and never +run). The AR authors therefore bypass Slurm and run jobs directly on compute +nodes over SSH. This scout is what tells them *which* node:gpu pairs are +actually idle: it SSHes every reachable node, reads real ``nvidia-smi`` usage, +and republishes a free-GPU inventory every ``INTERVAL`` seconds. + +Outputs (written atomically): + ``/free_gpus.json`` - machine-readable inventory + ``/free_gpus.txt`` - agent-facing table (this is what authors read) + +A GPU counts as free when its ``memory.used`` is below ``THRESHOLD_MIB``. + +Configuration (env vars, all optional): + GPU_SCOUT_OUT output directory (default /data/shared/zhizhousha/gpu-scout) + GPU_SCOUT_INTERVAL seconds per sweep (default 60) + GPU_SCOUT_THRESHOLD free cutoff MiB (default 2000) + GPU_SCOUT_SSH_TIMEOUT ssh connect secs (default 12) + GPU_SCOUT_WORKERS parallel probes (default 24) + GPU_SCOUT_NODES comma/space list to override sinfo discovery (optional) + +Run: python gpu_scout.py # loop forever + python gpu_scout.py --once # single sweep then exit (handy in tests) + +The agent-facing methodology lives in ``../GPU-RESOURCES.md``; keep the inventory +path here in sync with the path documented there. +""" +from __future__ import annotations + +import json +import os +import subprocess +import sys +import time +from concurrent.futures import ThreadPoolExecutor, as_completed +from datetime import datetime, timezone +from pathlib import Path + +OUT_DIR = Path(os.environ.get("GPU_SCOUT_OUT", "/data/shared/zhizhousha/gpu-scout")) +INTERVAL = int(os.environ.get("GPU_SCOUT_INTERVAL", "60")) +THRESHOLD_MIB = int(os.environ.get("GPU_SCOUT_THRESHOLD", "2000")) +SSH_TIMEOUT = int(os.environ.get("GPU_SCOUT_SSH_TIMEOUT", "12")) +MAX_WORKERS = int(os.environ.get("GPU_SCOUT_WORKERS", "24")) + +# Slurm node states we skip because the box is unreachable/unusable, not because +# it is busy - a busy ("alloc") node can still expose an idle GPU, and the whole +# point of this scout is to find those. +SKIP_STATE_PREFIXES = ("down", "drain", "drng", "fail", "maint", "boot", "unk", "pow") + + +def _json_path() -> Path: + return OUT_DIR / "free_gpus.json" + + +def _txt_path() -> Path: + return OUT_DIR / "free_gpus.txt" + + +def candidate_nodes() -> list[str]: + """Reachable compute nodes, from $GPU_SCOUT_NODES or ``sinfo``.""" + override = os.environ.get("GPU_SCOUT_NODES", "").replace(",", " ").split() + if override: + return sorted(set(override)) + try: + out = subprocess.run( + ["sinfo", "-h", "-N", "-o", "%N|%t"], + capture_output=True, text=True, timeout=20, + ).stdout + except (OSError, subprocess.TimeoutExpired): + return [] + nodes: dict[str, str] = {} + for line in out.splitlines(): + if "|" not in line: + continue + name, state = line.split("|", 1) + name = name.strip() + state = state.strip().lower().rstrip("*~#$+") + if not name: + continue + prev = nodes.get(name) + skip = state.startswith(SKIP_STATE_PREFIXES) + if prev is None or (prev.startswith(SKIP_STATE_PREFIXES) and not skip): + nodes[name] = state + return [n for n, s in nodes.items() if not s.startswith(SKIP_STATE_PREFIXES)] + + +def probe(node: str) -> tuple[str, list[dict] | None]: + """Return (node, GPU dicts) or (node, None) if the node is unreachable.""" + try: + res = subprocess.run( + [ + "ssh", "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=no", + "-o", f"ConnectTimeout={SSH_TIMEOUT}", node, + "nvidia-smi --query-gpu=index,memory.used,memory.total," + "utilization.gpu --format=csv,noheader,nounits", + ], + capture_output=True, text=True, timeout=SSH_TIMEOUT + 6, + ) + except (OSError, subprocess.TimeoutExpired): + return node, None + if res.returncode != 0: + return node, None + gpus: list[dict] = [] + for line in res.stdout.splitlines(): + parts = [p.strip() for p in line.split(",")] + if len(parts) < 4: + continue + try: + gpus.append({ + "gpu": int(parts[0]), + "mem_used_mib": int(parts[1]), + "mem_total_mib": int(parts[2]), + "util": int(parts[3]), + }) + except ValueError: + continue + return node, gpus + + +def sweep() -> dict: + nodes = candidate_nodes() + free: list[dict] = [] + by_node: dict[str, list[int]] = {} + unreachable: list[str] = [] + scanned = 0 + with ThreadPoolExecutor(max_workers=MAX_WORKERS) as ex: + futs = {ex.submit(probe, n): n for n in nodes} + for fut in as_completed(futs): + node, gpus = fut.result() + if gpus is None: + unreachable.append(node) + continue + scanned += 1 + idle = [] + for g in gpus: + if g["mem_used_mib"] < THRESHOLD_MIB: + idle.append(g["gpu"]) + free.append({ + "node": node, "gpu": g["gpu"], + "mem_used_mib": g["mem_used_mib"], "util": g["util"], + }) + if idle: + by_node[node] = sorted(idle) + free.sort(key=lambda d: (d["node"], d["gpu"])) + return { + "updated": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), + "threshold_mib": THRESHOLD_MIB, + "total_free": len(free), + "nodes_scanned": scanned, + "nodes_with_free": len(by_node), + "nodes_unreachable": sorted(unreachable), + "by_node": dict(sorted(by_node.items())), + "free_gpus": free, + } + + +def publish(inv: dict) -> None: + OUT_DIR.mkdir(parents=True, exist_ok=True) + tmp = _json_path().with_suffix(".json.tmp") + tmp.write_text(json.dumps(inv, indent=1), encoding="utf-8") + tmp.replace(_json_path()) + + lines = [ + f"# FREE GPU INVENTORY (updated {inv['updated']})", + f"# free = nvidia-smi memory.used < {inv['threshold_mib']} MiB. " + f"{inv['total_free']} free GPU(s) across {inv['nodes_with_free']} node(s).", + "# HOW TO USE: pick a node:gpu below, then run your job directly on it, e.g.", + "# ssh 'cd ; CUDA_VISIBLE_DEVICES= setsid nohup \\", + "# ./.venv/bin/python train.py > runs/.log 2>&1 &'", + "# Do NOT use sbatch/srun/Slurm. Re-run nvidia-smi on the node to confirm the", + "# GPU is still idle right before you launch (someone else may have grabbed it).", + "", + ] + for node, gpus in inv["by_node"].items(): + lines.append(f"{node}\tgpu {','.join(str(g) for g in gpus)}") + if not inv["by_node"]: + lines.append("(no free GPUs right now - wait and re-read this file)") + lines.append("") + lines.append( + f"TOTAL free: {inv['total_free']} GPU(s) on {inv['nodes_with_free']} node(s)" + ) + tmp = _txt_path().with_suffix(".txt.tmp") + tmp.write_text("\n".join(lines) + "\n", encoding="utf-8") + tmp.replace(_txt_path()) + + +def main() -> None: + once = "--once" in sys.argv[1:] + print( + f"[gpu-scout] publishing to {_txt_path()} " + f"{'once' if once else f'every {INTERVAL}s'}", + flush=True, + ) + while True: + t0 = time.time() + try: + inv = sweep() + publish(inv) + print( + f"[gpu-scout {inv['updated']}] free={inv['total_free']} " + f"nodes_with_free={inv['nodes_with_free']} " + f"scanned={inv['nodes_scanned']} " + f"unreachable={len(inv['nodes_unreachable'])}", + flush=True, + ) + except Exception as exc: # noqa: BLE001 - service loop must not die + print(f"[gpu-scout] sweep error: {exc}", flush=True) + if once: + return + time.sleep(max(5, INTERVAL - int(time.time() - t0))) + + +if __name__ == "__main__": + main() diff --git a/loom/templates/paper/wacv/main.tex b/loom/templates/paper/wacv/main.tex new file mode 100644 index 00000000..6a175d42 --- /dev/null +++ b/loom/templates/paper/wacv/main.tex @@ -0,0 +1,50 @@ +% WACV 2027 submission skeleton generated by Loom's AR pipeline. +% The style is vendored from the complete WACV 2027 template used by the +% claude-paper repository. + +\documentclass[10pt,twocolumn,letterpaper]{article} + +% Authors may switch algorithms to applications or datasets when the paper's +% contribution warrants it. Keep review mode until camera ready. +\usepackage[review,@@WACV_TRACK@@]{wacv} + +\usepackage{graphicx} +\usepackage{booktabs} +\usepackage{amsmath} +\usepackage{amssymb} +\usepackage{microtype} +\definecolor{wacvblue}{rgb}{0.21,0.49,0.74} +\usepackage[pagebackref,breaklinks,colorlinks,allcolors=wacvblue]{hyperref} + +\input{ar_macros.tex} + +\def\wacvPaperID{0000} +\def\confName{WACV} +\def\confYear{2027} + +\title{@@TITLE@@} +\author{Anonymous WACV submission\\Paper ID \wacvPaperID} + +\begin{document} +\maketitle + +\begin{abstract} +\input{sections/00_abstract} +\end{abstract} + +\input{sections/01_introduction} +\input{sections/02_related_work} +\input{sections/03_method} +\input{sections/04_experiments} +\input{sections/05_conclusion} + +{\small +\bibliographystyle{plainnat} +\bibliography{main} +} + +\clearpage +\appendix +\input{sections/06_appendix} + +\end{document} diff --git a/loom/templates/paper/wacv/wacv.sty b/loom/templates/paper/wacv/wacv.sty new file mode 100644 index 00000000..99856b89 --- /dev/null +++ b/loom/templates/paper/wacv/wacv.sty @@ -0,0 +1,447 @@ +% WACV 2027 style, adapted from the official ICCV 2025 / WACV 2025 author kits. +\NeedsTeXFormat{LaTeX2e}[1999/12/01] +\ProvidesPackage{wacv}[2027 LaTeX class for IEEE WACV] + +\RequirePackage{times} +\RequirePackage{xspace} +\RequirePackage[dvipsnames]{xcolor} +\RequirePackage{graphicx} +\RequirePackage{amsmath} +\RequirePackage{amssymb} +\RequirePackage{booktabs} +\RequirePackage[numbers,sort&compress]{natbib} +\setlength{\bibsep}{1pt plus 1pt minus 1pt} + +\RequirePackage{silence} +\hbadness=10000 \vbadness=10000 \vfuzz=30pt \hfuzz=30pt +\WarningFilter{latexfont}{Font shape declaration} +\WarningFilter{latex}{Font shape} +\WarningFilter[rebuttal]{latex}{No \author given} +\RequirePackage{etoolbox} + +\RequirePackage[format=plain,labelformat=simple,labelsep=period,font=small,compatibility=false]{caption} +\RequirePackage[font=footnotesize,skip=3pt,subrefformat=parens]{subcaption} + +\newtoggle{wacvfinal} +\newtoggle{wacvrebuttal} +\newtoggle{wacvpagenumbers} +\newtoggle{wacvalgorithms} +\newtoggle{wacvapplications} +\newtoggle{wacvdatasets} +\toggletrue{wacvfinal} +\togglefalse{wacvrebuttal} +\togglefalse{wacvpagenumbers} +\togglefalse{wacvalgorithms} +\togglefalse{wacvapplications} +\togglefalse{wacvdatasets} +\DeclareOption{review}{\togglefalse{wacvfinal}\toggletrue{wacvpagenumbers}} +\DeclareOption{rebuttal}{\togglefalse{wacvfinal}\toggletrue{wacvrebuttal}} +\DeclareOption{pagenumbers}{\toggletrue{wacvpagenumbers}} +\DeclareOption{applications}{\toggletrue{wacvapplications}} +\DeclareOption{algorithms}{\toggletrue{wacvalgorithms}} +\DeclareOption{datasets}{\toggletrue{wacvdatasets}} +\DeclareOption*{\PackageWarning{wacv}{Unknown option `\CurrentOption'}} +\ProcessOptions\relax + +\iftoggle{wacvrebuttal}{\ActivateWarningFilters[rebuttal]}{} + +\RequirePackage[hyphens]{url} +\Urlmuskip=0mu plus 1mu\relax + +% Inlined everyshi support. +\newcommand{\@EveryShipout@Hook}{} +\newcommand{\@EveryShipout@AtNextHook}{} +\newcommand*{\EveryShipout}[1]{\g@addto@macro\@EveryShipout@Hook{#1}} +\newcommand*{\AtNextShipout}[1]{\g@addto@macro\@EveryShipout@AtNextHook{#1}} +\newcommand{\@EveryShipout@Shipout}{% + \afterassignment\@EveryShipout@Test + \global\setbox\@cclv=% +} +\newcommand{\@EveryShipout@Test}{% + \ifvoid\@cclv\relax + \aftergroup\@EveryShipout@Output + \else + \@EveryShipout@Output + \fi +} +\newcommand{\@EveryShipout@Output}{% + \@EveryShipout@Hook + \@EveryShipout@AtNextHook + \gdef\@EveryShipout@AtNextHook{}% + \@EveryShipout@Org@Shipout\box\@cclv +} +\newcommand{\@EveryShipout@Org@Shipout}{} +\newcommand*{\@EveryShipout@Init}{% + \message{ABD: EveryShipout initializing macros}% + \let\@EveryShipout@Org@Shipout\shipout + \let\shipout\@EveryShipout@Shipout +} +\AtBeginDocument{\@EveryShipout@Init} + +% Inlined simplified eso-pic support. +\newcommand\LenToUnit[1]{#1\@gobble} +\newcommand\AtPageUpperLeft[1]{% + \begingroup + \@tempdima=0pt\relax\@tempdimb=\ESO@yoffsetI\relax + \put(\LenToUnit{\@tempdima},\LenToUnit{\@tempdimb}){#1}% + \endgroup +} +\newcommand\AtPageLowerLeft[1]{\AtPageUpperLeft{% + \put(0,\LenToUnit{-\paperheight}){#1}}} +\newcommand\AtPageCenter[1]{\AtPageUpperLeft{% + \put(\LenToUnit{.5\paperwidth},\LenToUnit{-.5\paperheight}){#1}}% +} +\newcommand\AtTextUpperLeft[1]{% + \begingroup + \setlength\@tempdima{1in}% + \ifodd\c@page + \advance\@tempdima\oddsidemargin + \else + \advance\@tempdima\evensidemargin + \fi + \@tempdimb=\ESO@yoffsetI\relax\advance\@tempdimb-1in\relax + \advance\@tempdimb-\topmargin + \advance\@tempdimb-\headheight\advance\@tempdimb-\headsep + \put(\LenToUnit{\@tempdima},\LenToUnit{\@tempdimb}){#1}% + \endgroup +} +\newcommand\AtTextLowerLeft[1]{\AtTextUpperLeft{% + \put(0,\LenToUnit{-\textheight}){#1}}} +\newcommand\AtTextCenter[1]{\AtTextUpperLeft{% + \put(\LenToUnit{.5\textwidth},\LenToUnit{-.5\textheight}){#1}}} +\newcommand{\ESO@HookI}{} +\newcommand{\ESO@HookII}{} +\newcommand{\ESO@HookIII}{} +\newcommand{\AddToShipoutPicture}{% + \@ifstar{\g@addto@macro\ESO@HookII}{\g@addto@macro\ESO@HookI}} +\newcommand{\ClearShipoutPicture}{\global\let\ESO@HookI\@empty} +\newcommand\ESO@isMEMOIR[1]{} +\@ifclassloaded{memoir}{\renewcommand\ESO@isMEMOIR[1]{#1}}{} +\newcommand{\@ShipoutPicture}{% + \bgroup + \@tempswafalse + \ifx\ESO@HookI\@empty\else\@tempswatrue\fi + \ifx\ESO@HookII\@empty\else\@tempswatrue\fi + \ifx\ESO@HookIII\@empty\else\@tempswatrue\fi + \if@tempswa + \@tempdima=1in\@tempdimb=-\@tempdima + \advance\@tempdimb\ESO@yoffsetI + \ESO@isMEMOIR{% + \advance\@tempdima\trimedge + \advance\@tempdima\paperwidth + \advance\@tempdima-\stockwidth + \if@twoside\ifodd\c@page\else + \advance\@tempdima-2\trimedge + \advance\@tempdima-\paperwidth + \advance\@tempdima\stockwidth + \fi\fi + \advance\@tempdimb\trimtop}% + \unitlength=1pt + \global\setbox\@cclv\vbox{% + \vbox{\let\protect\relax + \pictur@(0,0)(\strip@pt\@tempdima,\strip@pt\@tempdimb)% + \ESO@HookIII\ESO@HookI\ESO@HookII + \global\let\ESO@HookII\@empty + \endpicture}% + \nointerlineskip + \box\@cclv}% + \fi + \egroup +} +\EveryShipout{\@ShipoutPicture} +\RequirePackage{keyval} +\newif\ifESO@dvips\ESO@dvipsfalse +\newif\ifESO@texcoord\ESO@texcoordfalse + +\AtBeginDocument{% + \IfFileExists{color.sty}{% + \RequirePackage{color} + \let\ESO@color=\color\let\ESO@colorbox=\colorbox + \let\ESO@fcolorbox=\fcolorbox + }{} + \@ifundefined{Gin@driver}{}{% + \ifx\Gin@driver\@empty\else + \filename@parse{\Gin@driver}\def\reserved@a{dvips}% + \ifx\filename@base\reserved@a\ESO@dvipstrue\fi + \fi + }% + \ifx\pdfoutput\undefined\else + \ifx\pdfoutput\relax\else + \ifcase\pdfoutput\else + \ESO@dvipsfalse + \fi + \fi + \fi +} +\ifESO@texcoord + \def\ESO@yoffsetI{0pt}\def\ESO@yoffsetII{-\paperheight} +\else + \def\ESO@yoffsetI{\paperheight}\def\ESO@yoffsetII{0pt} +\fi + +\typeout{WACV 8.5 x 11-Inch Proceedings Style `wacv.sty'.} + +\font\wacvtenhv=phvb at 8pt +\font\elvbf=ptmb scaled 1100 +\font\tenbf=ptmb scaled 1000 + +\setlength{\textheight}{8.875in} +\setlength{\textwidth}{6.875in} +\setlength{\columnsep}{0.3125in} +\setlength{\topmargin}{0in} +\setlength{\headheight}{0in} +\setlength{\headsep}{0in} +\setlength{\parindent}{1pc} +\setlength{\oddsidemargin}{-0.1875in} +\setlength{\evensidemargin}{-0.1875in} + +\iftoggle{wacvpagenumbers}{}{\pagestyle{empty}} + +\AtBeginDocument{% + \@ifclassloaded{article}{}{% + \PackageError{wacv}{Package only meant to be used with document class `article'}% + {Change document class to `article'.} + } + \@ifclasswith{article}{10pt}{}{% + \PackageWarningNoLine{wacv}{WACV requires 10-point fonts} + } + \@ifclasswith{article}{twocolumn}{}{% + \PackageWarningNoLine{wacv}{WACV requires a two-column layout} + } + \@ifclasswith{article}{letterpaper}{}{% + \PackageWarningNoLine{wacv}{WACV requires letter paper} + } + \iftoggle{wacvfinal}{% + \@ifpackageloaded{hyperref}{}{% + \PackageWarningNoLine{wacv}{Package hyperref is recommended} + } + }{% + \@ifpackageloaded{hyperref}{% + \@ifpackagewith{hyperref}{pagebackref}{}{% + \PackageWarningNoLine{wacv}{Use hyperref with pagebackref in review mode} + } + }{% + \PackageWarningNoLine{wacv}{Package hyperref is recommended} + } + } +} + +\def\@maketitle{% + \newpage + \null + \iftoggle{wacvrebuttal}{\vspace*{-.3in}}{\vskip .375in} + \begin{center} + \iftoggle{wacvrebuttal}{{\large\bf\@title\par}}{{\Large\bf\@title\par}} + \iftoggle{wacvrebuttal}{\vspace*{-22pt}}{\vspace*{24pt}}{% + \large + \lineskip .5em + \begin{tabular}[t]{c} + \iftoggle{wacvfinal}{% + \@author + }{% + \iftoggle{wacvrebuttal}{}{% + \iftoggle{wacvalgorithms}{% + Anonymous \confName {\color[rgb]{.9,.1,.1}\fbox{Algorithms Track}} submission + }{% + \iftoggle{wacvapplications}{% + Anonymous \confName {\color[rgb]{.3,.6,.3}\fbox{Applications Track}} submission + }{% + \iftoggle{wacvdatasets}{% + Anonymous \confName {\color[rgb]{.3,.3,.6}\fbox{Datasets Track}} submission + }{% + \textbf{ERROR: select algorithms, applications, or datasets} + } + } + }\\ + \vspace*{1pt}\\ + Paper ID \wacvPaperID + } + } + \end{tabular} + \par + } + \vskip .5em + \vspace*{12pt} + \end{center} +} + +\def\abstract{% + \iftoggle{wacvpagenumbers}{}{\thispagestyle{empty}} + \centerline{\large\bf Abstract}% + \vspace*{12pt}\noindent + \it\ignorespaces +} +\def\endabstract{\vspace*{12pt}} + +\def\affiliation#1{\gdef\@affiliation{#1}} +\gdef\@affiliation{} + +\def\wacvsection{\@startsection{section}{1}{\z@}% + {-10pt plus -2pt minus -2pt}{7pt}{\large\bf}} +\def\wacvssect#1{\wacvsection*{#1}} +\def\wacvsect#1{\wacvsection{\texorpdfstring{\hskip -1em.~}{}#1}} +\def\section{\@ifstar\wacvssect\wacvsect} + +\def\wacvsubsection{\@startsection{subsection}{2}{\z@}% + {-8pt plus -2pt minus -2pt}{5pt}{\elvbf}} +\def\wacvssubsect#1{\wacvsubsection*{#1}} +\def\wacvsubsect#1{\wacvsubsection{\texorpdfstring{\hskip -1em.~}{}#1}} +\def\subsection{\@ifstar\wacvssubsect\wacvsubsect} + +\def\wacvsubsubsection{\@startsection{subsubsection}{3}{\z@}% + {-6pt plus -2pt minus -2pt}{3pt}{\tenbf}} +\def\wacvssubsubsect#1{\wacvsubsubsection*{#1}} +\def\wacvsubsubsect#1{\wacvsubsubsection{\texorpdfstring{\hskip -1em.~}{}#1}} +\def\subsubsection{\@ifstar\wacvssubsubsect\wacvsubsubsect} + +\iftoggle{wacvfinal}{% + \makeatletter + \providecommand{\@LN}[2]{} + \makeatother +}{% + \makeatletter + \newbox\wacvrulerbox + \newcount\wacvrulercount + \newdimen\wacvruleroffset + \newdimen\cv@lineheight + \newdimen\cv@boxheight + \newbox\cv@tmpbox + \newcount\cv@refno + \newcount\cv@tot + \newcount\cv@tmpc@ + \newcount\cv@tmpc + \def\fillzeros[#1]#2{% + \cv@tmpc@=#2\relax + \ifnum\cv@tmpc@<0 \cv@tmpc@=-\cv@tmpc@\fi + \cv@tmpc=1 + \loop + \ifnum\cv@tmpc@<10 + \else + \divide\cv@tmpc@ by 10 + \advance\cv@tmpc by 1 + \fi + \ifnum\cv@tmpc@=10 \cv@tmpc@=11\fi + \ifnum\cv@tmpc@>10 + \repeat + \ifnum#2<0 \advance\cv@tmpc1 -\fi + \loop + \ifnum\cv@tmpc<#1 0\advance\cv@tmpc1\fi + \ifnum\cv@tmpc<#1 + \repeat + \cv@tmpc@=#2\relax + \ifnum\cv@tmpc@<0 \cv@tmpc@=-\cv@tmpc@\fi + \the\cv@tmpc@ + } + \makeatother + + \RequirePackage[switch,mathlines]{lineno} + \renewcommand\linenumberfont{% + \wacvtenhv + \iftoggle{wacvalgorithms}{\color[rgb]{.9,.1,.1}}{% + \iftoggle{wacvdatasets}{\color[rgb]{.3,.3,.6}}{\color[rgb]{.3,.6,.3}}}} + \renewcommand\thelinenumber{\fillzeros[3]{\arabic{linenumber}}} + \setlength{\linenumbersep}{.75cm} + \RequirePackage{etoolbox} + + \newcommand*\linenomathpatch[1]{% + \expandafter\pretocmd\csname #1\endcsname{\linenomath}{}{}% + \expandafter\pretocmd\csname #1*\endcsname{\linenomath}{}{}% + \expandafter\apptocmd\csname end#1\endcsname{\endlinenomath}{}{}% + \expandafter\apptocmd\csname end#1*\endcsname{\endlinenomath}{}{}% + } + \newcommand*\linenomathpatchAMS[1]{% + \expandafter\pretocmd\csname #1\endcsname{\linenomathAMS}{}{}% + \expandafter\pretocmd\csname #1*\endcsname{\linenomathAMS}{}{}% + \expandafter\apptocmd\csname end#1\endcsname{\endlinenomath}{}{}% + \expandafter\apptocmd\csname end#1*\endcsname{\endlinenomath}{}{}% + } + \expandafter\ifx\linenomath\linenomathWithnumbers + \let\linenomathAMS\linenomathWithnumbers + \patchcmd\linenomathAMS{\advance\postdisplaypenalty\linenopenalty}{}{}{} + \else + \let\linenomathAMS\linenomathNonumbers + \fi + \linenumbers + \AtBeginDocument{% + \linenomathpatch{equation}% + \linenomathpatchAMS{gather}% + \linenomathpatchAMS{multline}% + \linenomathpatchAMS{align}% + \linenomathpatchAMS{alignat}% + \linenomathpatchAMS{flalign}% + } + + \def\wacvruler#1{% + \makevruler[12pt][#1][1][3][0.993\textheight]\usebox{\wacvrulerbox}} + \AddToShipoutPicture{% + \iftoggle{wacvalgorithms}{\color[rgb]{.9,.1,.1}}{% + \iftoggle{wacvdatasets}{\color[rgb]{.3,.3,.6}}{\color[rgb]{.3,.6,.3}}} + \def\pid{\parbox{1in}{\begin{center}\bf\sf + {\small\confName}\\\small\#\wacvPaperID\end{center}}} + \AtTextUpperLeft{% + \put(\LenToUnit{-65pt},\LenToUnit{45pt}){\pid} + \put(\LenToUnit{\textwidth-12pt},\LenToUnit{45pt}){\pid} + } + \AtTextUpperLeft{% + \put(0,\LenToUnit{1cm}){\parbox{\textwidth}{\centering\wacvtenhv + \confName~\confYear~Submission \#\wacvPaperID. + \iftoggle{wacvalgorithms}{\fbox{Algorithms Track.}}{% + \iftoggle{wacvapplications}{\fbox{Applications Track.}}{% + \iftoggle{wacvdatasets}{\fbox{Datasets Track.}}{}}} + CONFIDENTIAL REVIEW COPY. DO NOT DISTRIBUTE.}}} + } + } +} + +\renewcommand{\textfraction}{0.01} +\renewcommand{\floatpagefraction}{0.99} +\renewcommand{\topfraction}{0.99} +\renewcommand{\bottomfraction}{0.99} +\renewcommand{\dblfloatpagefraction}{0.99} +\renewcommand{\dbltopfraction}{0.99} +\setcounter{totalnumber}{99} +\setcounter{topnumber}{99} +\setcounter{bottomnumber}{99} + +\makeatletter +\DeclareRobustCommand\onedot{\futurelet\@let@token\@onedot} +\def\@onedot{\ifx\@let@token.\else.\null\fi\xspace} +\def\eg{\emph{e.g}\onedot} +\def\Eg{\emph{E.g}\onedot} +\def\ie{\emph{i.e}\onedot} +\def\Ie{\emph{I.e}\onedot} +\def\cf{\emph{cf}\onedot} +\def\Cf{\emph{Cf}\onedot} +\def\etc{\emph{etc}\onedot} +\def\vs{\emph{vs}\onedot} +\def\wrt{w.r.t\onedot} +\def\dof{d.o.f\onedot} +\def\iid{i.i.d\onedot} +\def\wolog{w.l.o.g\onedot} +\def\etal{\emph{et al}\onedot} +\makeatother + +\let\titleold\title +\renewcommand{\title}[1]{\titleold{#1}\newcommand{\thetitle}{#1}} +\def\maketitlesupplementary{% + \newpage + \twocolumn[ + \centering + \Large + \textbf{\thetitle}\\ + \vspace{0.5em}Supplementary Material\\ + \vspace{1.0em} + ] +} + +\AtEndPreamble{% + \usepackage[capitalize]{cleveref} + \crefname{section}{Sec.}{Secs.} + \Crefname{section}{Section}{Sections} + \Crefname{table}{Table}{Tables} + \crefname{table}{Tab.}{Tabs.} +} + +\RequirePackage[shortlabels,inline]{enumitem} +\setlist[itemize]{noitemsep,leftmargin=*,topsep=0em} +\setlist[enumerate]{noitemsep,leftmargin=*,topsep=0em} diff --git a/loom/web.py b/loom/web.py index dee627eb..29f389ef 100644 --- a/loom/web.py +++ b/loom/web.py @@ -4829,10 +4829,15 @@ def _rebuttal_resume_delivery_watchers() -> int: def _ar_venue_job(root: Path, slug: str, model: str) -> None: - """Deep-research the venue's last completed cycle, then persist the report.""" + """Deep-research the venue's last completed cycle, then persist the report. + + Chaining lives here rather than in the browser: a page reload must not be + able to lose the "and then propose ideas from it" half of the kickoff. + """ state = ar.read_ar_state(root, slug) log = _ar_logger(root, slug, ar.JOB_VENUE) res = ar.research_venue_cycle(state, model=model, on_line=log) + chain = bool(state.get("venue_chain_ideas")) if res.get("ok"): ar.update_ar_state( root, @@ -4840,16 +4845,27 @@ def _ar_venue_job(root: Path, slug: str, model: str) -> None: venue_report=res.get("report") or {}, venue_status="done", venue_error="", + venue_chain_ideas=False, venue_updated_at=_iso_now(), cost_usd=round( float(state.get("cost_usd") or 0.0) + float(res.get("cost") or 0.0), 4 ), ) print(f"[ar] {slug}: venue-cycle report ready", flush=True) + if chain: + log("report ready - generating ideas from it") + ar.update_ar_state(root, slug, ideas_status="running", ideas_error="") + _ar_run_async( + _ar_ideas_job, root, slug, 6, model, ar.IDEA_SOURCE_VENUE + ) else: log(f"failed: {res.get('error')}") ar.update_ar_state( - root, slug, venue_status="error", venue_error=str(res.get("error") or "") + root, + slug, + venue_status="error", + venue_error=str(res.get("error") or ""), + venue_chain_ideas=False, ) print(f"[ar] {slug}: venue research failed - {res.get('error')}", flush=True) @@ -6327,9 +6343,22 @@ def _ar_action( "ok": False, "error": f"another Studio job is running: {busy[0]}", }, 409 + venue_url = str(body.get("url", "")).strip() + if venue_url and not venue_url.startswith(("http://", "https://")): + return { + "ok": False, + "error": "venue URL must be an http(s) address", + }, 400 meta = read_meta(root, slug) model = str(body.get("model", "")).strip() or _ar_headless_model(meta) - ar.update_ar_state(root, slug, venue_status="running", venue_error="") + changes: dict[str, Any] = { + "venue_status": "running", + "venue_error": "", + "venue_chain_ideas": bool(body.get("chain_ideas")), + } + if venue_url: + changes["venue_url"] = venue_url + ar.update_ar_state(root, slug, **changes) _ar_run_async(_ar_venue_job, root, slug, model) return {"ok": True, "status": "running"}, 202 @@ -6354,7 +6383,13 @@ def _ar_action( return {"ok": True, "status": "running"}, 202 if str(state.get("venue_status")) == "running": return {"ok": False, "error": "venue research is still running"}, 409 - source = str(body.get("source", "")).strip() or ar.IDEA_SOURCE_PAPERS + source = str(body.get("source", "")).strip() or ( + # A last-cycle studio's natural grounding is its venue + # report; plain "Generate ideas" should not demand arXiv. + ar.IDEA_SOURCE_VENUE + if state.get("venue_kickoff") and state.get("venue_report") + else ar.IDEA_SOURCE_PAPERS + ) if source not in (ar.IDEA_SOURCE_PAPERS, ar.IDEA_SOURCE_VENUE): return {"ok": False, "error": "unknown idea source"}, 400 if source == ar.IDEA_SOURCE_VENUE and not state.get("venue_report"): @@ -6390,8 +6425,44 @@ def _ar_action( if not idea_ids: return {"ok": False, "error": "select at least one idea"}, 400 spawned, errors = _ar_spawn_children(root, slug, state, idea_ids) + # Spawning used to stop at the draft gate and wait for a manual + # "Start the draft" per paper. Operators want a studio's picks to + # begin writing immediately, so kick off each freshly spawned + # paper's author loop here (same seed + start the draft action + # runs). Papers that fail to start are reported, not fatal. + started: list[str] = [] + for item in spawned: + child = str(item.get("slug") or "") + if not child: + continue + try: + cstate = ar.read_ar_state(root, child) + paper_dir = ar.paper_root(root, child) + if not (paper_dir / "main.tex").is_file(): + ok_seed, msg_seed = ar.seed_paper_skeleton( + paper_dir, + str(cstate.get("venue") or ar.DEFAULT_VENUE), + cstate.get("idea"), + ) + if ok_seed: + ar.update_ar_state( + root, child, paper_dir=str(paper_dir) + ) + else: + errors.append(f"{child}: {msg_seed}") + continue + res = ar_manager.start(root, project_id, child) + if res.get("ok"): + started.append(child) + else: + errors.append( + f"{child}: {res.get('error') or 'failed to start'}" + ) + except Exception as exc: # noqa: BLE001 + errors.append(f"{child}: autostart failed: {exc}") payload = self._ar_payload(root, project_id, slug) payload["spawned"] = spawned + payload["started"] = started payload["errors"] = errors return payload, 200 @@ -9196,12 +9267,22 @@ def do_POST(self) -> None: # noqa: N802 ) ar_state: dict[str, Any] | None = None if kind == ar.KIND_AR: + venue_url = str(body.get("ar_venue_url", "")).strip() + if venue_url and not venue_url.startswith(("http://", "https://")): + st, b, h = _json_bytes( + {"error": "venue URL must be an http(s) address"}, + 400, + ) + self._send(st, b, h) + return ar_state = ar.new_studio_state( direction=str(body.get("ar_direction", "")), custom_direction=str(body.get("ar_custom_direction", "")), venue=str(body.get("ar_venue", "")), mode=str(body.get("ar_mode", "")), seed_idea=str(body.get("ar_seed_idea", "")), + venue_url=venue_url, + venue_kickoff=bool(body.get("ar_venue_kickoff")), max_rounds=body.get("ar_max_rounds", ar.DEFAULT_MAX_ROUNDS), ) # AR asks for the paper's content, not a goal to interview diff --git a/loom/web_static/factory.html b/loom/web_static/factory.html index 78ba3ade..dc96736e 100644 --- a/loom/web_static/factory.html +++ b/loom/web_static/factory.html @@ -319,6 +319,8 @@

Start a studio

+ + diff --git a/loom/web_static/factory.js b/loom/web_static/factory.js index 9d21d8b2..7977d3ee 100644 --- a/loom/web_static/factory.js +++ b/loom/web_static/factory.js @@ -418,46 +418,32 @@ function paperMiningState(state, papers) { function venueResearchState(state) { const status = String(state.venue_status || ''); const report = state.venue_report || {}; - if (status === 'running') return 'deep-researching the venue\u2019s last cycle\u2026'; + if (status === 'running') { + return state.venue_chain_ideas + ? 'deep-researching the last cycle\u2026 ideas will follow automatically' + : 'deep-researching the venue\u2019s last cycle\u2026'; + } if (status === 'error') return `failed: ${state.venue_error || 'unknown error'}`; if (Object.keys(report).length) { - const queued = S.venueIdeasQueued === S.slug ? ' \u00b7 ideas queued next' : ''; - return `report ready: ${report.cycle || 'last cycle'}${queued}`; + return `report ready: ${report.cycle || 'last cycle'}`; } return 'not researched yet'; } -// The "ideas from last cycle" button is one press for the whole chain: run the -// venue research if the report is missing, then generate from it as soon as -// the report lands. The queued flag carries the slug so switching studios -// mid-research cannot fire ideas at the wrong one. -async function maybeQueueVenueIdeas(state) { - if (S.venueIdeasQueued !== S.slug) return; - const status = String(state.venue_status || ''); - if (status === 'running') return; - S.venueIdeasQueued = ''; - if (status !== 'done' || !Object.keys(state.venue_report || {}).length) return; - if (state.ideas_status === 'running') return; - await act(S.slug, 'ideas', { - count: Number(el('studio-count').value || 6), - source: 'venue', - }, 'Venue idea generation'); - loadTask(); -} - function renderStudio(d, state) { el('studio-title').textContent = d.title || S.slug; el('studio-eyebrow').textContent = `Studio · ${String(state.venue || '').toUpperCase()} · ${d.direction_label || ''}`; - el('studio-sub').textContent = state.seed_idea - || `Mining ${d.direction_label} and proposing ideas grounded in what it finds.`; + el('studio-sub').textContent = state.venue_kickoff + ? `Researching what ${String(state.venue || '').toUpperCase()} rewarded last cycle and proposing ideas from it.` + : (state.seed_idea + || `Mining ${d.direction_label} and proposing ideas grounded in what it finds.`); const logs = d.logs || {}; renderLog('papers-log', logs.papers, state.papers_status === 'running'); renderLog('ideas-log', logs.ideas, state.ideas_status === 'running'); renderLog('venue-log', logs.venue, state.venue_status === 'running'); renderSearchSettings(d, state); - maybeQueueVenueIdeas(state); const papers = state.papers || []; const ideas = state.ideas || []; @@ -540,14 +526,17 @@ function renderSteps(state, papers, ideas) { const spawned = ideas.filter((i) => i.status === 'spawned').length; const running = (job) => state[`${job}_status`] === 'running'; // A studio seeded from your own idea can go straight to step 2; pointing - // "current" at mining would say the opposite. + // "current" at mining would say the opposite. A last-cycle studio never + // mines arXiv at all, so that step disappears entirely. const seeded = state.mode === 'seed'; + const venueStudio = Boolean(state.venue_kickoff); const steps = [ { id: 'mine', done: papers.length > 0, - optional: seeded, + optional: seeded || venueStudio, + hidden: venueStudio, state: seeded && !papers.length && !state.papers_status ? 'optional — this studio starts from your idea' : paperMiningState(state, papers), @@ -556,6 +545,7 @@ function renderSteps(state, papers, ideas) { id: 'ideas', done: ideas.length > 0, state: running('ideas') ? 'generating, a few minutes…' + : venueStudio && running('venue') ? 'waiting for the last-cycle report…' : ideas.length ? `${ideas.length} ideas` : (state.ideas_error || 'not run yet'), }, @@ -576,11 +566,16 @@ function renderSteps(state, papers, ideas) { steps.forEach((s, i) => { const node = document.querySelector(`.rf-step[data-step="${s.id}"]`); if (!node) return; + node.hidden = Boolean(s.hidden); node.classList.toggle('is-done', s.done); node.classList.toggle('is-current', i === current); const label = el(`step-${s.id}-state`); if (label) label.textContent = s.state; }); + // Renumber the visible steps so a hidden one leaves no gap behind. + document.querySelectorAll('.rf-step:not([hidden]) .rf-step__n').forEach( + (badge, i) => { badge.textContent = String(i + 1); }, + ); // A step whose input does not exist yet cannot run, so say so on the button // rather than letting it be pressed and answer with an error. @@ -599,16 +594,20 @@ function renderSteps(state, papers, ideas) { why: busy ? 'a Studio job is already running' : (!terms.length ? 'add or suggest search terms first' : 'select at least one arXiv category'), }); + const hasVenueReport = Object.keys(state.venue_report || {}).length > 0; setAction('btn-ideas', { - ok: !busy && (papers.length > 0 || state.mode === 'seed'), - why: busy ? 'a job is already running' : 'mine the field first, or start the studio from your own idea', + ok: !busy && ( + venueStudio ? hasVenueReport : (papers.length > 0 || state.mode === 'seed') + ), + why: busy ? 'a job is already running' + : venueStudio ? 'run the last-cycle research first' + : 'mine the field first, or start the studio from your own idea', }); - const hasVenueReport = Object.keys(state.venue_report || {}).length > 0; setAction('btn-ideas-venue', { ok: !busy, why: busy ? 'a job is already running' : '', - label: running('venue') ? 'Researching last cycle…' - : S.venueIdeasQueued === S.slug ? 'Ideas queued…' + label: running('venue') + ? (state.venue_chain_ideas ? 'Researching… ideas will follow' : 'Researching last cycle…') : hasVenueReport ? 'Ideas from last cycle' : 'Research last cycle → ideas', }); const venueLabel = el('step-venue-state'); @@ -1507,8 +1506,7 @@ el('btn-ideas-venue').addEventListener('click', async () => { source: 'venue', }, 'Venue idea generation'); } else { - const d = await act(S.slug, 'venue', {}, 'Venue research'); - if (d) S.venueIdeasQueued = S.slug; + await act(S.slug, 'venue', { chain_ideas: true }, 'Venue research'); } loadTask(); }); @@ -1631,6 +1629,7 @@ el('btn-new-studio').addEventListener('click', () => { if (cat.default_max_rounds) el('new-rounds').value = cat.default_max_rounds; } el('studio-modal-status').textContent = ''; + syncStudioModalMode(); el('studio-modal').hidden = false; el('new-title').focus(); }); @@ -1642,13 +1641,25 @@ el('new-title').addEventListener('keydown', (ev) => { el('new-direction').addEventListener('change', () => { el('new-custom-direction').hidden = el('new-direction').value !== 'custom'; }); +// Last-cycle studios take their direction from what the venue rewarded, so +// the Direction picker disappears in that mode; the Venue picker stays - it +// is the one input the deep research cannot do without. +function syncStudioModalMode() { + const mode = document.querySelector('input[name="new-mode"]:checked').value; + el('new-seed-label').textContent = mode === 'seed' + ? 'What the paper should be about' + : 'What the paper should be about (optional)'; + const venueMode = mode === 'venue'; + // The URL names the venue in this mode, so both catalog pickers disappear: + // direction comes from what the venue rewarded, the venue from the page. + el('new-direction').closest('.rf-row').hidden = venueMode; + el('new-custom-direction').hidden = venueMode + || el('new-direction').value !== 'custom'; + el('new-venue-url-label').hidden = !venueMode; + el('new-venue-url').hidden = !venueMode; +} document.querySelectorAll('input[name="new-mode"]').forEach((radio) => { - radio.addEventListener('change', () => { - const seeded = document.querySelector('input[name="new-mode"]:checked').value === 'seed'; - el('new-seed-label').textContent = seeded - ? 'What the paper should be about' - : 'What the paper should be about (optional)'; - }); + radio.addEventListener('change', syncStudioModalMode); }); el('btn-studio-create').addEventListener('click', async () => { const title = el('new-title').value.trim(); @@ -1662,27 +1673,43 @@ el('btn-studio-create').addEventListener('click', async () => { // runs the studio in auto mode, but the first job is the venue deep // research, and ideas chain from its report instead of an arXiv haul. const venueKickoff = mode === 'venue'; + const venueUrl = el('new-venue-url').value.trim(); + if (venueKickoff && !venueUrl) { + status.textContent = 'Paste the venue page URL — it decides which venue gets researched.'; + return; + } + if (venueKickoff && !/^https?:\/\//.test(venueUrl)) { + status.textContent = 'The venue page must be an http(s) URL.'; + return; + } status.textContent = 'Creating…'; try { const { meta } = await api('/api/tasks', { method: 'POST', body: JSON.stringify({ title, kind: 'ar', agent: 'cursor', - ar_direction: direction, - ar_custom_direction: el('new-custom-direction').value.trim(), + // In last-cycle mode the hidden Direction picker must not leak its + // stale value into the studio: the direction IS the venue's taste. + ar_direction: venueKickoff ? 'custom' : direction, + ar_custom_direction: venueKickoff + ? 'Open direction: follow whatever this venue rewarded in its last completed cycle.' + : el('new-custom-direction').value.trim(), ar_venue: el('new-venue').value, ar_mode: venueKickoff ? 'auto' : mode, ar_seed_idea: seed, + ar_venue_url: venueKickoff ? venueUrl : '', + ar_venue_kickoff: venueKickoff, ar_max_rounds: Number(el('new-rounds').value || 10), }), }); el('studio-modal').hidden = true; - el('new-title').value = ''; el('new-seed').value = ''; + el('new-title').value = ''; el('new-seed').value = ''; el('new-venue-url').value = ''; openStudio(meta.slug); if (venueKickoff) { - const started = await act(meta.slug, 'venue', {}, 'Venue research'); + const started = await act( + meta.slug, 'venue', { url: venueUrl, chain_ideas: true }, 'Venue research', + ); if (started) { - S.venueIdeasQueued = meta.slug; toast('Deep-researching the venue\u2019s last cycle \u2014 ideas will follow automatically.'); } loadTask(); diff --git a/tests/test_venue_ideas.py b/tests/test_venue_ideas.py index e3d25451..c1d91342 100644 --- a/tests/test_venue_ideas.py +++ b/tests/test_venue_ideas.py @@ -60,6 +60,34 @@ def test_normalize_venue_report_bounds_and_drops_empty_titles() -> None: assert len(report["summary"]) == 2000 +def test_operator_venue_url_leads_the_research_prompt(monkeypatch) -> None: + captured: dict[str, str] = {} + + def fake_run(prompt, model="", timeout=0, on_line=None): + captured["prompt"] = prompt + return {"ok": True, "text": _REPORT_JSON, "cost": 0.0} + + monkeypatch.setattr(ar, "_run_headless", fake_run) + + with_url = ar.new_studio_state( + direction="multimodal", + venue="wacv", + venue_url="https://wacv.example/awards ", + venue_kickoff=True, + ) + assert with_url["venue_url"] == "https://wacv.example/awards" + assert with_url["venue_kickoff"] is True + assert ar.new_studio_state(direction="multimodal")["venue_kickoff"] is False + assert ar.research_venue_cycle(with_url)["ok"] + assert "START HERE" in captured["prompt"] + assert "https://wacv.example/awards" in captured["prompt"] + + without_url = ar.new_studio_state(direction="multimodal", venue="wacv") + assert ar.research_venue_cycle(without_url)["ok"] + assert "START HERE" not in captured["prompt"] + assert "Use your own web search" in captured["prompt"] + + def test_research_venue_cycle_parses_fenced_report(monkeypatch) -> None: state = ar.new_studio_state(direction="multimodal", venue="wacv") monkeypatch.setattr( @@ -152,6 +180,40 @@ def test_venue_job_persists_report_and_cost(tmp_path: Path, monkeypatch) -> None assert state["cost_usd"] == 0.5 +def test_venue_job_chains_idea_generation_server_side( + tmp_path: Path, monkeypatch +) -> None: + root, slug = _studio(tmp_path) + ar.update_ar_state( + root, slug, venue_status="running", venue_chain_ideas=True + ) + monkeypatch.setattr( + web.ar, + "research_venue_cycle", + lambda state, model="", on_line=None: { + "ok": True, + "report": ar.normalize_venue_report( + {"cycle": "WSDM 2026", "best_papers": [{"title": "Winner"}]} + ), + "cost": 0.1, + }, + ) + launched: list = [] + monkeypatch.setattr( + web, "_ar_run_async", lambda fn, *args: launched.append((fn, args)) + ) + + web._ar_venue_job(root, slug, "claude-test") + + state = ar.read_ar_state(root, slug) + assert state["venue_status"] == "done" + assert state["venue_chain_ideas"] is False + assert state["ideas_status"] == "running" + assert launched == [ + (web._ar_ideas_job, (root, slug, 6, "claude-test", ar.IDEA_SOURCE_VENUE)) + ] + + def test_venue_job_records_error(tmp_path: Path, monkeypatch) -> None: root, slug = _studio(tmp_path) ar.update_ar_state(root, slug, venue_status="running")