diff --git a/AGENTS.md b/AGENTS.md index df7e6dc..319a911 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,7 @@ ## 学习路线(进度) - [x] Phase 1:理解核心概念(concepts/,8 篇) -- [x] Phase 2:形成自己的观点(thinking/,9 篇,持续中) +- [x] Phase 2:形成自己的观点(thinking/,10 篇,持续中) - [x] Phase 3:选一个小项目实践(practice/,1 个 Ralph Demo) - [x] Phase 4:记录反馈迭代(feedback/,1 篇,持续中) - [x] Phase 5:输出可展示的作品(works/,22 篇翻译 + 1 篇原创 + 2 篇外部中文收录) diff --git a/README.en.md b/README.en.md index ed763af..bed59bb 100644 --- a/README.en.md +++ b/README.en.md @@ -109,7 +109,7 @@ harness-engineering/ │ ├── 06-harness-... # Harness definition (Fowler control-theory extension) │ └── 07-spec-as-product.md # Spec as product (Symphony extension) │ -├── thinking/ # Phase 2: Independent analysis (9 articles) +├── thinking/ # Phase 2: Independent analysis (10 articles) ├── practice/ # Phase 3: Hands-on experiments (1 Ralph Demo) ├── feedback/ # Phase 4: Lessons learned (1 article) ├── works/ # Phase 5: Shareable outputs (22 translations + 1 original + 2 external Chinese captures) @@ -123,7 +123,7 @@ Each subdirectory has its own `AGENTS.md` explaining its purpose and conventions ## 🚀 Learning Path - [x] **Phase 1: Understand core concepts** — 8 concept notes covering OpenAI's six concepts + Fowler's control-theory extension + Symphony's spec-as-product -- [x] **Phase 2: Form your own opinions** — 9 independent analyses (ongoing) +- [x] **Phase 2: Form your own opinions** — 10 independent analyses (ongoing) - [x] **Phase 3: Pick a small project to practice** — Ralph Demo completed (321s, $0.31) - [x] **Phase 4: Record feedback & iterations** — 1 article (ongoing) - [x] **Phase 5: Produce shareable work** — 22 professional translations + 1 original synthesis + 2 external Chinese captures diff --git a/README.md b/README.md index 4b1d1c0..82167ba 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ harness-engineering/ │ ├── 06-harness-... # Harness 精确定义(Fowler 控制论扩展) │ └── 07-spec-as-product.md # 约束即产品(Symphony 延伸) │ -├── thinking/ # Phase 2:独立思考与质疑(9 篇) +├── thinking/ # Phase 2:独立思考与质疑(10 篇) ├── practice/ # Phase 3:小项目实验(1 个 Ralph Demo) ├── feedback/ # Phase 4:踩坑与迭代心得(1 篇) ├── works/ # Phase 5:可展示的作品(22 篇翻译 + 1 篇原创 + 2 篇外部中文收录) @@ -122,7 +122,7 @@ harness-engineering/ ## 🚀 学习路线 - [x] **Phase 1:理解核心概念** — 8 篇概念笔记,覆盖 OpenAI 六大概念 + Fowler 控制论扩展 + Symphony 约束即产品 -- [x] **Phase 2:形成自己的观点** — 9 篇独立思考(持续中) +- [x] **Phase 2:形成自己的观点** — 10 篇独立思考(持续中) - [x] **Phase 3:选一个小项目实践** — Ralph Demo 完成(321 秒,$0.31) - [x] **Phase 4:记录反馈迭代** — 1 篇(持续中) - [x] **Phase 5:输出可展示的作品** — 22 篇专业翻译 + 1 篇原创综合分析 + 2 篇外部中文收录 diff --git a/scripts/check-research-harness.py b/scripts/check-research-harness.py new file mode 100755 index 0000000..291fde8 --- /dev/null +++ b/scripts/check-research-harness.py @@ -0,0 +1,326 @@ +#!/usr/bin/env python3 +"""Check an ML research repo for harness drift. + +This is a deterministic structural validator. It does not judge whether a +research claim is true; it checks that the repo still exposes the links needed +for a human or agent to audit that claim. +""" + +from __future__ import annotations + +import argparse +import re +import sys +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +try: + import yaml # type: ignore +except Exception: # pragma: no cover - dependency is optional by design. + yaml = None + + +REQUIRED_DIRS = [ + "code", + "infra", + "research", + "deliverables", + "data", + "artifacts", + "memory", +] + +REQUIRED_FILES = [ + "README.md", + "AGENTS.md", + "PROJECT.md", + "DECISIONS.md", + "research/claims.yaml", + "research/evidence.yaml", + "research/experiment-ledger.yaml", + "artifacts/result-index.yaml", + "memory/current-status.md", +] + +EXPERIMENT_REQUIRED_FILES = [ + "experiment-card.md", + "config.yaml", + "linked-claims.yaml", +] + +PRIVATE_PATTERNS = [ + re.compile(r"(?i)\b(token|password|passwd|secret|api[_-]?key)\s*[:=]\s*['\"]?[^'\"\s]+"), + re.compile(r"-----BEGIN (RSA |OPENSSH |EC |DSA )?PRIVATE KEY-----"), +] + +ABSOLUTE_PATH_PATTERN = re.compile(r"(? None: + self.root = root.resolve() + self.warn_only_privacy = warn_only_privacy + self.findings: list[Finding] = [] + self.claim_ids: set[str] = set() + self.evidence_ids: set[str] = set() + self.experiment_ids: set[str] = set() + + def error(self, code: str, path: str, message: str) -> None: + self.findings.append(Finding("FAIL", code, path, message)) + + def warn(self, code: str, path: str, message: str) -> None: + self.findings.append(Finding("WARN", code, path, message)) + + def rel(self, path: Path) -> str: + try: + return str(path.relative_to(self.root)) + except ValueError: + return str(path) + + def exists(self, rel_path: str) -> bool: + return (self.root / rel_path).exists() + + def text(self, rel_path: str) -> str: + path = self.root / rel_path + try: + return path.read_text(encoding="utf-8") + except UnicodeDecodeError: + return path.read_text(errors="replace") + + def load_yaml(self, rel_path: str) -> Any: + path = self.root / rel_path + if not path.exists(): + return None + if yaml is None: + self.warn( + "YAML001", + rel_path, + "PyYAML is not installed; using regex-only checks for this file", + ) + return None + try: + return yaml.safe_load(path.read_text(encoding="utf-8")) + except Exception as exc: + self.error("YAML002", rel_path, f"YAML parse failed: {exc}") + return None + + def collect_ids_from_yaml_or_text(self, rel_path: str, prefix: str) -> set[str]: + ids: set[str] = set() + data = self.load_yaml(rel_path) + if data is not None: + for item in walk(data): + if isinstance(item, dict) and isinstance(item.get("id"), str): + value = item["id"] + if value.startswith(prefix + "-"): + ids.add(value) + if ids: + return ids + if self.exists(rel_path): + ids.update(match for match in ID_PATTERN.findall(self.text(rel_path)) if match.startswith(prefix + "-")) + return ids + + def check_structure(self) -> None: + for dirname in REQUIRED_DIRS: + if not (self.root / dirname).is_dir(): + self.error("S1", dirname, "required top-level directory is missing") + for filename in REQUIRED_FILES: + if not (self.root / filename).is_file(): + self.error("S1", filename, "required harness file is missing") + + def check_gitignore(self) -> None: + gitignore = self.root / ".gitignore" + if not gitignore.exists(): + self.warn("S1", ".gitignore", "missing .gitignore; private overlays and run logs may drift into git") + return + text = gitignore.read_text(encoding="utf-8", errors="replace") + for pattern in ["infra/private/", "runs/", "*.log"]: + if pattern not in text: + self.warn("S1", ".gitignore", f"recommended ignore pattern missing: {pattern}") + + def check_experiment_contracts(self) -> None: + experiments = self.root / "code" / "experiments" + if not experiments.exists(): + self.error("S4", "code/experiments", "experiment directory is missing") + return + for child in sorted(experiments.iterdir()): + if not child.is_dir() or not EXPERIMENT_DIR_PATTERN.match(child.name): + continue + self.experiment_ids.add(child.name.split("-", 1)[0]) + for filename in EXPERIMENT_REQUIRED_FILES: + if not (child / filename).is_file(): + self.error("S4", self.rel(child / filename), "experiment is missing required file") + linked_claims = child / "linked-claims.yaml" + if linked_claims.exists() and not ID_PATTERN.search(linked_claims.read_text(encoding="utf-8", errors="replace")): + self.error("S4", self.rel(linked_claims), "experiment has no linked claim or hypothesis id") + + def check_references(self) -> None: + self.claim_ids = self.collect_ids_from_yaml_or_text("research/claims.yaml", "CLM") + self.evidence_ids = self.collect_ids_from_yaml_or_text("research/evidence.yaml", "EVD") + + if self.exists("research/claims.yaml") and not self.claim_ids: + self.error("S3", "research/claims.yaml", "no CLM-* claim ids found") + if self.exists("research/evidence.yaml") and not self.evidence_ids: + self.warn("S3", "research/evidence.yaml", "no EVD-* evidence ids found yet") + + self.check_unknown_refs("research/evidence.yaml", "CLM", self.claim_ids) + self.check_unknown_refs("research/claims.yaml", "EVD", self.evidence_ids) + self.check_unknown_refs("artifacts/result-index.yaml", "EVD", self.evidence_ids) + self.check_unknown_refs("memory/phase-dashboard.yaml", "CLM", self.claim_ids, required=False) + self.check_required_fields( + "research/evidence.yaml", + ["id", "experiment", "config", "run", "artifact", "commit", "data_split", "metric"], + "S3", + ) + self.check_required_fields( + "artifacts/result-index.yaml", + ["id", "source_experiment", "source_config", "source_commit"], + "S3", + ) + + ledger = self.load_yaml("research/experiment-ledger.yaml") + if isinstance(ledger, list): + for idx, item in enumerate(ledger): + if not isinstance(item, dict): + continue + label = f"research/experiment-ledger.yaml[{idx}]" + if not any(key in item for key in ["experiment", "id"]): + self.error("S3", label, "ledger row lacks experiment/id") + if not item.get("claims") and not item.get("hypotheses"): + self.error("S3", label, "ledger row is not linked to claims or hypotheses") + if not item.get("infra_target"): + self.warn("S3", label, "ledger row does not declare infra_target") + + def check_required_fields(self, rel_path: str, fields: list[str], code: str) -> None: + data = self.load_yaml(rel_path) + if data is None: + return + rows = data if isinstance(data, list) else [data] + for idx, item in enumerate(rows): + if not isinstance(item, dict): + self.error(code, f"{rel_path}[{idx}]", "row is not a mapping") + continue + missing = [field for field in fields if not item.get(field)] + if missing: + self.error(code, f"{rel_path}[{idx}]", "missing required field(s): " + ", ".join(missing)) + + def check_unknown_refs(self, rel_path: str, prefix: str, known: set[str], *, required: bool = True) -> None: + if not self.exists(rel_path): + if required: + self.error("S3", rel_path, "reference source file is missing") + return + refs = {match for match in ID_PATTERN.findall(self.text(rel_path)) if match.startswith(prefix + "-")} + unknown = sorted(refs - known) + for ref in unknown: + self.error("S3", rel_path, f"references unknown {prefix} id: {ref}") + + def check_paths_and_privacy(self) -> None: + for path in sorted(self.root.rglob("*")): + if not path.is_file() or any(part in EXCLUDED_PARTS for part in path.parts): + continue + rel = self.rel(path) + if path.stat().st_size > 2_000_000: + continue + text = path.read_text(encoding="utf-8", errors="replace") + for pattern in PRIVATE_PATTERNS: + if pattern.search(text): + message = "possible secret or private key committed" + if self.warn_only_privacy: + self.warn("S8", rel, message) + else: + self.error("S8", rel, message) + if rel.startswith(("code/configs/", "code/experiments/", "research/", "artifacts/", "deliverables/paper/")): + for match in ABSOLUTE_PATH_PATTERN.findall(text): + self.error("S5", rel, f"contains bare absolute path rooted at /{match}; use infra/paths logical paths") + + def check_memory(self) -> None: + status = self.root / "memory" / "current-status.md" + if not status.exists(): + return + text = status.read_text(encoding="utf-8", errors="replace").strip() + if len(text) < 120: + self.warn("S9", "memory/current-status.md", "status file is very short; handoff state may be incomplete") + lowered = text.lower() + for needle in ["next", "下一步", "blocked", "阻塞", "risk", "风险"]: + if needle in lowered: + return + self.warn("S9", "memory/current-status.md", "status file does not mention next step, blocker, or risk") + + def run(self) -> int: + self.check_structure() + self.check_gitignore() + self.check_experiment_contracts() + self.check_references() + self.check_paths_and_privacy() + self.check_memory() + return self.report() + + def report(self) -> int: + for finding in self.findings: + print(f"[{finding.severity}] {finding.code} {finding.path}: {finding.message}") + failures = [finding for finding in self.findings if finding.severity == "FAIL"] + warnings = [finding for finding in self.findings if finding.severity == "WARN"] + print() + if failures: + print(f"research harness check failed: {len(failures)} failure(s), {len(warnings)} warning(s)") + return 1 + print(f"research harness check passed: 0 failure(s), {len(warnings)} warning(s)") + return 0 + + +def walk(value: Any) -> list[Any]: + items = [value] + if isinstance(value, dict): + for child in value.values(): + items.extend(walk(child)) + elif isinstance(value, list): + for child in value: + items.extend(walk(child)) + return items + + +def main(argv: list[str]) -> int: + parser = argparse.ArgumentParser(description="Check an ML research repo for harness drift.") + parser.add_argument("--root", default=".", help="repository root to check") + parser.add_argument( + "--warn-only-privacy", + action="store_true", + help="report possible secrets as warnings instead of failures", + ) + args = parser.parse_args(argv) + + root = Path(args.root) + if not root.exists(): + print(f"root does not exist: {root}", file=sys.stderr) + return 2 + if not (root / ".git").exists(): + print(f"warning: {root} does not look like a git repository", file=sys.stderr) + return Checker(root, warn_only_privacy=args.warn_only_privacy).run() + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/skills/research-repo-auditor/SKILL.md b/skills/research-repo-auditor/SKILL.md new file mode 100644 index 0000000..e7024f5 --- /dev/null +++ b/skills/research-repo-auditor/SKILL.md @@ -0,0 +1,64 @@ +--- +name: research-repo-auditor +description: Audit ML research repositories for harness drift, claim/evidence traceability, experiment-contract violations, infra reproducibility gaps, baseline fairness risks, paper-number provenance, and agent handoff quality. Use when asked to review or validate a diffusion, benchmark, dataset, or ML research project repo structure; check whether experiments follow the repo harness; or design/operate a research-repo audit before submission, release, or long-running agent work. +--- + +# Research Repo Auditor + +Use this skill to audit whether an ML research repo still behaves like a claim-driven, infra-aware, reproducible research system. + +## Workflow + +1. Read `AGENTS.md`, `PROJECT.md`, `memory/current-status.md`, and the relevant `research/*.yaml` files first. +2. Run the deterministic validator if present: + + ```bash + python3 scripts/check-research-harness.py + ``` + +3. Treat validator failures as structural defects, then perform a semantic audit of the research links. +4. Produce a short audit report with findings first, ordered by severity, with file references. + +## Mechanical Checks + +Verify these contracts: + +- `code/experiments/E###-*` has `experiment-card.md`, `config.yaml`, and `linked-claims.yaml`. +- Every experiment links to at least one `CLM-*` or `HYP-*`. +- `research/evidence.yaml` links evidence to experiment, config, run, artifact, commit, data split, and metric. +- `artifacts/result-index.yaml` links results back to evidence and source experiment/config/commit. +- `deliverables/paper/` does not introduce untracked numbers; paper tables and figures point back to evidence IDs. +- Configs and ledgers use logical paths instead of bare machine-specific absolute paths. +- `infra/private/`, run logs, and local overlays are ignored by git. +- `memory/current-status.md` states current goal, blocker/risk, and next smallest action. + +## Semantic Audit + +Ask these questions after the mechanical pass: + +- Does each experiment actually test the claim it claims to support? +- Has partial evidence been promoted to a supported claim too early? +- Are baseline comparisons fair on data split, metric, training budget, sampling budget, and checkpoint source? +- Are negative results reflected in claim status, risks, or next actions? +- Are reviewer risks connected to concrete actions? +- Can a new agent reproduce the next run without relying on shell history or private memory? + +## Output Shape + +Use this structure: + +```text +Findings +- [Severity] file:line — issue, evidence, and required fix. + +Open Questions +- Only include questions that block a correct audit. + +Validation +- Command run and result, or why it could not run. + +Residual Risk +- What the audit could not prove mechanically. +``` + +Prefer concrete defects over general advice. Do not mark the repo healthy if paper numbers, evidence, artifacts, and configs cannot be traced end to end. diff --git a/thinking/AGENTS.md b/thinking/AGENTS.md index 7c78f0d..43e59d8 100644 --- a/thinking/AGENTS.md +++ b/thinking/AGENTS.md @@ -22,6 +22,7 @@ | [software-project-complexity-in-the-ai-era.md](software-project-complexity-in-the-ai-era.md) | AI 时代的软件项目复杂度 | 6 维(上下文压力 / 可提示性 / 探索收敛 / 状态纠缠 / 暗知识 / 验证成本)+ 胶囊化分母 + 重构/复刻终局指标 | | [subagent-is-child-runtime.md](subagent-is-child-runtime.md) | Subagent 是 child runtime | 与 Claude Code 架构逆向互证:subagent = tool-call-triggered child session + context projection + evidence-return contract | | [why-this-project-exists.md](why-this-project-exists.md) | 为什么有这个项目 | 项目宗旨:把 Harness Engineering 从"概念集合"变成"可复刻的产品构建方法论"——借用《诡秘之主》"学徒途径"隐喻 | +| [ml-research-project-repo-structure.md](ml-research-project-repo-structure.md) | ML Research 项目仓库结构 | 经典 ML research repo 应组织为 code system、infra system、research ledger、deliverables、project memory,并用 validator + auditor skill 防止证据链漂移 | ## 写作方向参考 diff --git a/thinking/ml-research-project-repo-structure.md b/thinking/ml-research-project-repo-structure.md new file mode 100644 index 0000000..decc59b --- /dev/null +++ b/thinking/ml-research-project-repo-structure.md @@ -0,0 +1,2157 @@ +# 经典 ML Research Project 的 Repo 应该长什么样 + +## 核心论点 + +一个经典 ML research project,尤其是 diffusion model、new method、new dataset、new benchmark 这类项目,不应该被组织成简单的: + +```text +paper/ +src/ +train.py +``` + +它更应该被组织成一个可运行、可验证、可复现、可接续的研究系统: + +```text +ml-research repo + = lab system + + human-facing deliverables + + project memory +``` + +更具体地说: + +```text +lab/ 生成证据的实验系统 +deliverables/ 给人看的最终表达 +memory/ 管理实验事实如何走向文章 +``` + +这个结构的目标,不是为了让目录看起来整齐,而是为了让整个研究项目围绕一条证据链运转: + +```text +research claim + -> lab experiment + -> lab infra target + -> run logs + -> artifact index + -> evidence ledger + -> deliverable table / figure +``` + +如果这条链断了,项目就会退化成一堆脚本、日志、checkpoint 和论文草稿。跑了很多实验,但没人能稳定回答: + +```text +这个数字来自哪个 run? +这个 run 用的是哪个 config? +这个 config 对应哪个 commit? +这个实验支持哪个 claim? +这个 claim 进了论文哪张表? +baseline 是否公平? +换一台服务器还能不能复现? +``` + +--- + +## 1. 推荐目录结构 + +一个较完整的 diffusion / ML research repo 可以长这样: + +```text +/ + README.md + AGENTS.md + PROJECT.md + DECISIONS.md + + lab/ + code/ + src/ + my_method/ + models/ + diffusion/ + data/ + training/ + sampling/ + evaluation/ + metrics/ + utils/ + + configs/ + defaults.yaml + model/ + data/ + train/ + eval/ + sampler/ + experiment/ + baseline/ + benchmark/ + infra/ + + scripts/ + train.py + sample.py + evaluate.py + compare_baselines.py + prepare_data.py + submit_job.py + collect_results.py + + baselines/ + wrappers/ + configs/ + reproduction/ + + benchmarks/ + tasks/ + metrics/ + protocols/ + runners/ + + tests/ + unit/ + smoke/ + regression/ + + experiments/ + E001-baseline-reproduction/ + E002-main-method/ + E003-ablation-no-x/ + + infra/ + README.md + inventory.yaml + targets/ + local-mac.template.yaml + workstation.template.yaml + gpu-server.template.yaml + cluster.template.yaml + paths/ + logical-paths.yaml + path-map.template.yaml + environments/ + uv/ + pyproject.toml + uv.lock + conda/ + cuda121-torch.yml + cuda124-torch.yml + docker/ + Dockerfile + compose.yaml + schedulers/ + local/ + slurm/ + runai/ + tmux/ + launch/ + train.template.sh + eval.template.sh + sample.template.sh + submit.template.sh + storage/ + datasets.yaml + checkpoints.yaml + artifacts.yaml + logs.yaml + dependencies.yaml + external-scripts.yaml + probes/ + check_cuda.py + check_dataset.py + smoke_train.py + smoke_sample.py + private/ + README.md + + research/ + METHOD.md + DATASET.md + BENCHMARK.md + claims.yaml + hypotheses.yaml + evidence.yaml + baselines.yaml + experiment-ledger.yaml + ablation-matrix.yaml + comparison-matrix.yaml + negative-results.md + reviewer-risks.md + + data/ + cards/ + splits/ + manifests/ + preprocessing/ + checksums/ + + runs/ + README.md + .gitignore + + artifacts/ + README.md + model-index.yaml + result-index.yaml + sample-index.yaml + .gitignore + + deliverables/ + paper/ + main.tex + sections/ + figures/ + tables/ + appendix/ + + reviews/ + technical-review.md + novelty-review.md + reproducibility-review.md + reviewer-risk-register.yaml + + rebuttal/ + reviews.yaml + response-plan.md + promises.yaml + final-response.md + + slides/ + project-page/ + artifact-release/ + + memory/ + current-status.md + phase-dashboard.yaml + change-control.yaml + lab/ + active-experiments.yaml + run-queue.yaml + infra-status.yaml + data-status.yaml + blockers.yaml + paper/ + manuscript-status.md + table-status.yaml + figure-status.yaml + section-status.yaml + reviewer-risks.yaml + rebuttal-status.yaml + bridge/ + claim-to-evidence.yaml + evidence-to-table.yaml + result-to-figure.yaml + promises.yaml + handoff-log.md + archive/ + lab/ + paper/ + bridge/ + handoffs/ + gc/ + retention-policy.yaml + compaction-log.md + tombstones.yaml +``` + +这不是第一天必须全部实现的模板。 + +第一天最重要的是把层级关系想清楚: + +```text +lab/ 证据生成层:代码、实验、基础设施、数据、运行、产物、研究账本 +deliverables/ 人类表达层:论文、review、rebuttal、slides、项目页、release +memory/ 状态控制层:实验状态、文章状态、实验到文章的桥接状态 +``` + +--- + +## 2. 顶层文件 + +### README.md + +`README.md` 面向外部读者和新加入的人,回答: + +```text +这个项目研究什么? +核心贡献是什么? +如何快速跑一个 smoke test? +如何复现主要结果? +当前 release / paper / benchmark 状态是什么? +``` + +不要把 README 写成实验日志。README 应该是入口,而不是事实数据库。 + +### AGENTS.md + +`AGENTS.md` 面向 Agent,回答: + +```text +进入项目后先读哪里? +哪些目录是 lab、deliverables、memory? +改代码后必须跑哪些 smoke checks? +新增实验后必须写回哪些 ledger? +哪些信息不能提交进公开 repo? +哪些节点必须 human approval? +``` + +对 ML research project 来说,`AGENTS.md` 的关键价值是固定读写协议: + +```text +进入时: +AGENTS.md -> memory/current-status.md -> lab/research/claims.yaml -> relevant lab/code config or lab/experiments entry + +离开时: +update experiment/result -> update evidence -> update memory/current-status.md -> report validation +``` + +### PROJECT.md + +`PROJECT.md` 是项目简介,面向人和 Agent 都可读。 + +它应该写: + +- 研究问题; +- 目标会议或目标产出; +- 主方法简介; +- 数据集和 benchmark 范围; +- 当前阶段; +- 成功标准; +- 不在范围内的内容。 + +### DECISIONS.md + +`DECISIONS.md` 记录关键决策。 + +ML research 项目里,很多决定会在后期变成暗知识: + +- 为什么选这个 baseline,不选另一个; +- 为什么放弃某个 metric; +- 为什么数据 split 这样划; +- 为什么某个 ablation 不再跑; +- 为什么从 conda 转向 uv 或 Docker; +- 为什么某台服务器不再用于主实验。 + +这些都应该进入 decision log。否则几周后,人和 Agent 都会重新争论同一件事。 + +--- + +## 3. lab/code/:机器可执行的研究系统 + +`lab/code/` 是工程主体。凡是可以被运行、测试、复现、提交到服务器的东西,都应该在这里。 + +它回答: + +```text +这个方法如何实现? +这个实验如何跑? +baseline 如何接入? +benchmark 如何评估? +``` + +### lab/code/src/ + +`src/` 放核心算法和训练逻辑。 + +对 diffusion project,可以拆成: + +```text +lab/code/src/my_method/ + models/ # UNet, DiT, VAE, text encoder adapters + diffusion/ # noise schedule, denoising objective, sampler internals + data/ # dataset, transforms, dataloader + training/ # train loop, loss, optimizer, checkpoint + sampling/ # sampler, guidance, generation pipeline + evaluation/ # eval loop, evaluator wrappers + metrics/ # FID, IS, CLIPScore, task-specific metrics + utils/ # seed, logging, distributed helpers +``` + +原则: + +1. **训练入口要薄。** `train.py` 不应该承载全部逻辑,它只负责加载 config、初始化组件、调用训练循环。 +2. **代码要 device-agnostic。** 不要在模块里到处写 `.cuda()`,统一由 runtime / trainer 控制 device。 +3. **seed 要可控。** 每个 run 的 seed 应该写入 config 和 result summary。 +4. **checkpoint 要可 resume。** 只保存权重不够,optimizer、scheduler、epoch、global step、scaler 都要可恢复。 +5. **shape 和数据契约要显式。** diffusion 项目里的 tensor shape、latent space、conditioning 格式非常容易变成隐式假设。 + +### lab/code/configs/ + +`configs/` 是实验组合层。 + +一个实验应该能由 config 完整描述,而不是靠 shell history 复原。 + +建议拆成: + +```text +lab/code/configs/ + defaults.yaml + model/ + data/ + train/ + eval/ + sampler/ + experiment/ + baseline/ + benchmark/ + infra/ +``` + +`experiment/` 里可以有: + +```text +E002-main-method.yaml +E003-ablation-no-x.yaml +E004-low-data-regime.yaml +``` + +这些 config 应该引用 model/data/train/eval/sampler/baseline/infra 的组件配置。 + +关键原则: + +```text +实验身份 = code commit + config + data split + infra target + seed +``` + +缺一项,复现链就不完整。 + +### lab/code/scripts/ + +`scripts/` 是可执行入口。 + +典型脚本: + +```text +train.py +sample.py +evaluate.py +compare_baselines.py +prepare_data.py +submit_job.py +collect_results.py +``` + +这些脚本应该稳定、薄、可组合。不要把一次性实验逻辑写死在脚本里;一次性变化应该通过 config 表达。 + +### lab/code/baselines/ + +`baselines/` 单独治理 baseline。 + +baseline 是论文可信度的核心,不应该散落在 notebook、外部 repo clone、临时 shell 脚本里。 + +建议包含: + +```text +lab/code/baselines/ + wrappers/ # 统一调用接口 + configs/ # baseline-specific configs + reproduction/ # 复现原论文/官方数字的记录 +``` + +每个 baseline 至少记录: + +- 来源 repo; +- commit / release; +- 原始环境要求; +- 是否修改过代码; +- 使用的数据 split; +- 复现的原论文指标; +- 与本方法比较时是否共享数据处理、metric、采样预算。 + +核心原则: + +> baseline 不是“能跑就行”,而是要能证明比较是公平的。 + +### lab/code/benchmarks/ + +如果项目提出新 benchmark,`benchmarks/` 会成为核心代码产物。 + +它可以包含: + +```text +benchmarks/ + tasks/ + metrics/ + protocols/ + runners/ +``` + +其中: + +- `tasks/` 定义任务; +- `metrics/` 定义评估指标; +- `protocols/` 定义输入输出、数据 split、评估预算; +- `runners/` 统一运行本方法和 baseline。 + +如果 benchmark 规则只写在论文里,不写成代码和 protocol,后续很难复现。 + +### lab/experiments/ + +`lab/experiments/` 放可复现实验定义,不是最终结果仓库。 + +每个实验目录可以有: + +```text +lab/experiments/E002-main-method/ + experiment-card.md + config.yaml + expected-outputs.yaml + linked-claims.yaml + notes.md +``` + +`experiment-card.md` 应该回答: + +```text +这个实验验证什么? +对应哪个 claim? +用哪个 dataset / split? +比较哪些 baseline? +跑几个 seed? +成功标准是什么? +预计产出哪张表或图? +``` + +实验目录不是为了替代 `lab/research/evidence.yaml`,而是给“怎么跑”一个稳定入口。 + +### lab/code/tests/ + +`tests/` 保障代码本身不坏。 + +建议分三类: + +```text +tests/ + unit/ # 小函数、小模块 + smoke/ # tiny dataloader / tiny train / tiny sample + regression/ # 已经踩过的 bug +``` + +对 diffusion 项目,smoke test 很重要: + +- 数据集能否读取一个 batch; +- 模型 forward 是否 shape 正确; +- loss 是否 finite; +- 训练 2-10 steps 是否能跑通; +- sampler 是否能产出正确 shape; +- eval runner 是否能消费生成结果。 + +不要等主实验跑 20 小时后才发现 config 或 dataloader 坏了。 + +--- + +## 4. lab/infra/:运行基底和计算目标 + +`lab/infra/` 回答: + +```text +这个项目在哪里跑? +每台机器有什么能力? +路径如何映射? +用什么 Python / CUDA / Docker 环境? +用什么 scheduler 提交任务? +数据、checkpoint、log 放哪里? +当前 target 能不能跑? +``` + +把它叫 `lab/infra/`,而不是 `envs/`,是因为这里的问题远远超过 Python 环境。 + +一个 ML 项目可能同时有: + +- 本地 Mac:写代码、跑 tiny smoke test; +- 本地 workstation:单卡 debug; +- GPU server:主训练; +- SLURM 集群:多 seed ablation; +- RunAI / Kubernetes:批量任务; +- 不同 CUDA / torch / xformers / flash-attn 组合; +- 不同数据挂载路径; +- 不同 checkpoint 和 log 存储策略。 + +这些都属于 infra。 + +### lab/infra/inventory.yaml + +记录计算目标清单。 + +示例: + +```yaml +targets: + local_mac: + role: control_plane_and_smoke_test + gpu: none + suitable_for: + - config_validation + - dataloader_smoke_test + - tiny_model_test + + gpu_server_a: + role: single_node_training + gpu: A100 + scheduler: tmux + suitable_for: + - main_training + - sampling + - baseline_reproduction + + cluster_b: + role: batch_experiments + scheduler: slurm + suitable_for: + - ablations + - multi_seed_benchmark_runs +``` + +公开 repo 中建议用逻辑名,不要提交真实内网地址、账号或 token。 + +### lab/infra/targets/ + +`targets/` 描述不同类型机器的模板。 + +```text +targets/ + local-mac.template.yaml + workstation.template.yaml + gpu-server.template.yaml + cluster.template.yaml +``` + +每个 target template 可以描述: + +- device type; +- GPU 数量和显存; +- CUDA / driver 约束; +- scheduler; +- storage mount; +- 是否允许长任务; +- 适合运行哪些实验; +- 禁止事项。 + +### lab/infra/paths/ + +`paths/` 解决路径差异。 + +代码和 config 不应该到处写绝对路径。应该用逻辑路径: + +```yaml +logical_paths: + project_root: "{PROJECT_ROOT}" + data_root: "{DATA_ROOT}" + checkpoint_root: "{CHECKPOINT_ROOT}" + run_root: "{RUN_ROOT}" + artifact_root: "{ARTIFACT_ROOT}" +``` + +然后每台机器用 path map overlay 映射。 + +公开 repo 里放: + +```text +lab/infra/paths/path-map.template.yaml +``` + +真实路径可以放在 gitignored 的: + +```text +lab/infra/private/local-mac.yaml +lab/infra/private/gpu-server-a.yaml +lab/infra/private/cluster-b.yaml +``` + +如果项目是完全私有的,也可以记录真实路径。但如果将来要开源、投稿 artifact、或多人协作,最好从一开始就区分 template 和 private overlay。 + +### lab/infra/environments/ + +这里才是原来狭义的 `envs/`。 + +```text +environments/ + uv/ + pyproject.toml + uv.lock + conda/ + cuda121-torch.yml + cuda124-torch.yml + docker/ + Dockerfile + compose.yaml +``` + +它回答: + +```text +Python 依赖是什么? +torch / CUDA 组合是什么? +是否有 Docker 复现路径? +本地测试和服务器训练是否用同一套锁文件? +``` + +原则: + +- 环境规格进 repo; +- 真实虚拟环境目录不进 repo; +- 可重建比“我机器上能跑”重要; +- 如果多服务器环境不同,要显式记录差异。 + +### lab/infra/schedulers/ 和 lab/infra/launch/ + +`schedulers/` 记录调度方式: + +```text +schedulers/ + local/ + slurm/ + runai/ + tmux/ +``` + +`launch/` 记录启动模板: + +```text +launch/ + train.template.sh + eval.template.sh + sample.template.sh + submit.template.sh +``` + +这能避免每次靠临时命令启动长实验。 + +一个实验启动应该尽量由这几件事组成: + +```text +experiment config + + infra target + + launch template + + path map +``` + +### lab/infra/storage/ + +`storage/` 记录数据、checkpoint、artifact、log 的存储策略。 + +```text +storage/ + datasets.yaml + checkpoints.yaml + artifacts.yaml + logs.yaml +``` + +ML 项目通常有大量不能进 git 的东西: + +- 原始数据; +- 预处理数据; +- checkpoint; +- 生成样本; +- tensorboard / wandb logs; +- 大表格; +- artifact release 包。 + +这些不进 git,但必须有索引和路径策略。 + +### lab/infra/dependencies.yaml + +`dependencies.yaml` 记录所有会影响复现的非标准依赖。 + +它不是替代 `pyproject.toml`、`uv.lock`、`requirements.txt` 或 conda environment,而是解释这些依赖为什么存在、从哪里来、是否可控。 + +危险的依赖漂移包括: + +```text +pip install -e ../SOME_LOCAL_PACKAGE +from SOME_LOCAL_PACKAGE import * +requirements.txt 里出现 file:// 或本机路径 +代码依赖某个没有登记的外部 repo clone +``` + +每个非标准依赖至少应该记录: + +```yaml +- name: SOME_LOCAL_PACKAGE + kind: local_path | git_repo | wheel | system_binary | private_package + source: "../SOME_LOCAL_PACKAGE" + pinned_ref: null + reproducible: false + purpose: "Used by data preprocessing." + owner: human + replacement_plan: "Vendor or publish before release." + risk: "Not reproducible outside local machine." +``` + +默认规则: + +- 本地路径依赖默认视为不可复现; +- git 依赖必须 pin 到 commit; +- 私有包必须标明 release 前的替代计划; +- system binary 必须记录版本和安装方式; +- 没有登记的外部依赖不能进入主实验链路。 + +### lab/infra/external-scripts.yaml + +`external-scripts.yaml` 记录项目调用的外部脚本、下载脚本和非 repo 内命令。 + +危险的脚本漂移包括: + +```text +curl ... | bash +python /Users/me/private/preprocess.py +bash ~/scripts/launch_train.sh +从外部 repo 复制脚本但没有 commit / hash +``` + +每条外部脚本依赖至少应该记录: + +```yaml +- id: EXT-001 + name: official_dataset_preprocess + source_url: "https://github.com/example/repo/scripts/preprocess.py" + pinned_ref: "abc1234" + local_copy: "lab/code/scripts/preprocess_dataset.py" + sha256: "..." + purpose: "Build dataset manifests." + allowed_in_release: true +``` + +如果一个脚本不能 pin、不能 hash、不能进入 repo,它只能出现在 `lab/infra/private/`,并且不能作为论文主结果的唯一复现路径。 + +### lab/infra/probes/ + +`probes/` 是 infra 的传感器。 + +```text +probes/ + check_cuda.py + check_dataset.py + smoke_train.py + smoke_sample.py +``` + +Agent 或人接手项目前,应该能先跑 probe: + +```text +当前 target 可用吗? +CUDA / torch / xformers 是否匹配? +dataset manifest 能读吗? +能跑 10 step tiny training 吗? +能生成一个 tiny sample 吗? +``` + +这比文档里写“服务器可用”可靠。 + +### lab/infra/private/ + +`private/` 是 gitignored 的真实机器 overlay。 + +它可以存: + +- 真实路径; +- 本地机器特定配置; +- 私有服务器别名; +- 本地 cache 位置; +- 非公开 storage mount。 + +但不要存: + +- token; +- password; +- 私钥; +- 不该共享的数据路径; +- 会泄露服务器内部结构的敏感信息。 + +--- + +## 5. lab/research/:研究事实和证据账本 + +`lab/research/` 不是论文目录,也不是代码目录。 + +它是研究是否成立的账本,回答: + +```text +我们到底提出了什么? +核心 hypothesis 是什么? +哪些 claim 已经被 evidence 支持? +哪些实验只是 partial? +哪些 baseline 还不公平? +哪些 negative result 改变了研究方向? +reviewer 可能攻击哪里? +``` + +### lab/research/METHOD.md + +描述新方法。 + +它应该比论文方法部分更直接: + +- 方法解决什么问题; +- 与 baseline 的关键差异; +- 核心机制; +- 预期优势; +- 已知限制; +- 需要哪些实验支持。 + +### lab/research/DATASET.md + +如果项目提出新数据集,`DATASET.md` 是数据集的研究说明。 + +它回答: + +- 数据集来源; +- 构造方法; +- 规模; +- split; +- 标注或生成流程; +- 潜在 bias; +- 许可和使用限制; +- 为什么这个数据集能支撑研究问题。 + +### lab/research/BENCHMARK.md + +如果项目提出新 benchmark,`BENCHMARK.md` 描述 benchmark 的任务和协议。 + +它回答: + +- benchmark 衡量什么能力; +- 任务输入输出; +- metric; +- baseline set; +- 评估预算; +- 防止 overfitting 的设计; +- 与已有 benchmark 的区别。 + +### lab/research/claims.yaml + +这是核心文件。 + +论文里的每个主张都应该有 claim ID。 + +示例: + +```yaml +- id: CLM-002 + claim: "The proposed diffusion training objective improves sample quality in low-data regimes." + status: partial + evidence: + - EVD-014 + - EVD-018 + paper_locations: + - deliverables/paper/sections/experiments.tex + - deliverables/paper/tables/table2.tex + risks: + - RSK-006 + next_actions: + - ACT-021 +``` + +没有 claim ID,实验和论文就很容易脱节。 + +### lab/research/hypotheses.yaml + +记录尚未被证明的研究假设。 + +Hypothesis 和 claim 的区别: + +```text +hypothesis 是待验证的研究判断; +claim 是准备写进论文、需要证据支持的主张。 +``` + +### lab/research/evidence.yaml + +记录证据。 + +证据可以来自: + +- 实验结果; +- ablation; +- baseline comparison; +- 数据集统计; +- human study; +- 理论分析; +- failure analysis。 + +每条 evidence 应该能追溯到: + +```text +experiment id +config +run id +artifact +commit +data split +metric +``` + +### lab/research/baselines.yaml + +记录 baseline 状态。 + +它回答: + +```text +哪些 baseline 已经复现? +哪些 baseline 只是引用数字? +哪些 baseline 使用官方 checkpoint? +哪些 baseline 重新训练? +哪些 comparison 可能不公平? +``` + +Baseline 状态应该直接影响 reviewer risk。 + +### lab/research/experiment-ledger.yaml + +连接实验和研究对象。 + +示例: + +```yaml +- experiment: E004-low-data-regime + claims: + - CLM-002 + hypotheses: + - HYP-003 + baselines: + - BASE-001 + - BASE-004 + status: running + infra_target: gpu_server_a + result_summary: null +``` + +### lab/research/ablation-matrix.yaml + +记录 ablation 维度。 + +它防止这种情况: + +```text +论文里说 component X 很重要,但其实没有控制其他变量。 +``` + +### lab/research/comparison-matrix.yaml + +记录方法对比矩阵。 + +它应该包含: + +- method; +- dataset; +- split; +- metric; +- sampling budget; +- training budget; +- checkpoint source; +- status; +- paper table。 + +### lab/research/negative-results.md + +记录负结果和失败路线。 + +这非常重要。 + +失败如果不记录,后面会发生两件事: + +1. 人或 Agent 重复跑已经失败过的方向; +2. 论文叙事会不自觉忽略真实边界。 + +好的 negative result 记录应该写: + +```text +尝试了什么? +为什么当时认为可行? +失败现象是什么? +它反驳了哪个 hypothesis,还是只说明实现/预算不够? +下一步是 drop、narrow,还是 rerun? +``` + +### lab/research/reviewer-risks.md + +记录 reviewer 可能攻击的问题。 + +例如: + +- baseline 不够强; +- 数据集规模太小; +- metric 不合适; +- 新方法只是工程 trick; +- 计算预算不公平; +- diffusion sampling steps 不一致; +- 没有跨数据集泛化; +- 新 benchmark 可能过拟合本方法。 + +这些风险应该连接到 action,而不是停留在担忧。 + +--- + +## 6. deliverables/:给人看的最终产出 + +`deliverables/` 是表达层,不是真相源头。 + +它包含: + +```text +deliverables/ + paper/ + reviews/ + rebuttal/ + slides/ + project-page/ + artifact-release/ +``` + +这个目录名比把 `paper/`、`reviews/` 平铺在 repo 根目录更准确,因为它们本质上都是面向人的产出。 + +### deliverables/paper/ + +论文是最终表达,不是唯一事实来源。 + +```text +paper/ + main.tex + sections/ + figures/ + tables/ + appendix/ +``` + +论文里的数字和 claim 应该能回溯到: + +```text +deliverables/paper/table + -> lab/research/evidence.yaml + -> lab/artifacts/result-index.yaml + -> lab/runs/ + -> lab/experiments/ + -> lab/code/configs/ + -> commit +``` + +不要让论文草稿成为事实源头。论文可以改叙事,但不能偷偷改变实验事实。 + +### deliverables/reviews/ + +内部 review 也属于 human-facing deliverable。 + +```text +reviews/ + technical-review.md + novelty-review.md + reproducibility-review.md + reviewer-risk-register.yaml +``` + +这些文件的作用是把人类判断结构化: + +- 技术是否成立; +- novelty 是否足够; +- 实验是否公平; +- 复现是否可信; +- reviewer 可能如何质疑。 + +### deliverables/rebuttal/ + +投稿后阶段单独放。 + +```text +rebuttal/ + reviews.yaml + response-plan.md + promises.yaml + final-response.md +``` + +`promises.yaml` 很关键。rebuttal 中承诺 camera-ready 会补的东西,必须可追踪。 + +### deliverables/slides/ + +放 presentation、talk、poster。 + +Slides 经常会生成新的图和简化叙事。它们同样应该引用 lab/research/evidence,而不是产生孤立数字。 + +### deliverables/project-page/ + +项目主页、demo 页面、可视化说明。 + +如果项目包含 diffusion samples,project page 很容易变成选择性展示。最好让展示样例连接到 lab/artifacts/sample-index.yaml。 + +### deliverables/artifact-release/ + +开源或 artifact evaluation 包。 + +它应该包含: + +- release checklist; +- package manifest; +- reproducibility instructions; +- environment requirements; +- model / data license notes。 + +--- + +## 7. lab/data/:数据资产与数据契约 + +`lab/data/` 不一定存原始数据,尤其大数据通常不进 git。 + +它应该存数据说明和可复现契约: + +```text +lab/data/ + cards/ + splits/ + manifests/ + preprocessing/ + checksums/ +``` + +### lab/data/cards/ + +Dataset card。 + +记录: + +- 数据来源; +- 许可; +- 字段; +- 规模; +- 采集或生成流程; +- bias; +- 使用限制; +- 与研究问题的关系。 + +### lab/data/splits/ + +固定 train / val / test 划分。 + +如果 split 不固定,baseline comparison 就不可信。 + +### lab/data/manifests/ + +记录文件列表、样本 ID、版本。 + +Manifest 是跨服务器复现的关键。不同服务器上的真实路径可以不同,但 manifest 应该一致。 + +### lab/data/preprocessing/ + +记录预处理流程。 + +预处理如果只存在于一次性脚本或 notebook,后续很难解释实验差异。 + +### lab/data/checksums/ + +用于确认不同服务器上的数据一致。 + +这和 `lab/infra/paths/` 配合: + +```text +same logical dataset + -> different physical paths on different machines + -> same manifest / checksum +``` + +--- + +## 8. lab/runs/:运行时日志,不做长期事实源 + +`lab/runs/` 是训练和评估过程产生的临时运行记录,通常 gitignored。 + +它可以包含: + +```text +lab/runs/ + 2026-06-23_E002_seed0/ + stdout.log + train.log + tensorboard/ + wandb/ + samples/ +``` + +但长期要保留的结论不应该只躺在 `lab/runs/`。 + +关键结果应该汇总到: + +```text +lab/experiments/E###/result-summary.md +lab/research/evidence.yaml +lab/artifacts/result-index.yaml +``` + +否则三个月后你会有一堆日志,但不知道哪些可信、哪些进了论文。 + +--- + +## 9. lab/artifacts/:大产物索引 + +`lab/artifacts/` 通常不直接存大文件,而是存索引。 + +```text +lab/artifacts/ + model-index.yaml + result-index.yaml + sample-index.yaml +``` + +它记录: + +- checkpoint; +- generated samples; +- metrics; +- tables; +- figures; +- artifact release packages; +- storage location; +- hash; +- source experiment; +- source config; +- source commit。 + +例如: + +```yaml +- id: CKPT-018 + experiment: E002-main-method + run: RUN-2026-06-23-001 + config: lab/code/configs/experiment/E002-main-method.yaml + commit: abc1234 + storage: logical:checkpoint_root/E002/seed0/step100000.pt + sha256: "..." + supports: + - EVD-014 +``` + +核心链路是: + +```text +checkpoint -> experiment -> config -> commit -> dataset split -> claim +``` + +这条链必须可追溯。否则论文里的一个数字来自哪个 checkpoint 会变成谜。 + +--- + +## 10. memory/:项目状态控制面板 + +`memory/` 是给人和 Agent 接续项目用的。 + +它不替代 `lab/`,也不替代 `deliverables/`。它只记录项目当前状态,以及实验事实如何走向文章。 + +```text +memory/ + current-status.md + phase-dashboard.yaml + lab/ + active-experiments.yaml + run-queue.yaml + infra-status.yaml + data-status.yaml + blockers.yaml + paper/ + manuscript-status.md + table-status.yaml + figure-status.yaml + section-status.yaml + reviewer-risks.yaml + rebuttal-status.yaml + bridge/ + claim-to-evidence.yaml + evidence-to-table.yaml + result-to-figure.yaml + promises.yaml + handoff-log.md + archive/ + lab/ + paper/ + bridge/ + handoffs/ + gc/ + retention-policy.yaml + compaction-log.md + tombstones.yaml +``` + +这些目录的核心职责是: + +```text +memory/lab/ 记实验推进状态 +memory/paper/ 记文章推进状态 +memory/bridge/ 记实验事实如何进入文章 +memory/archive/ 记不再影响当前行动的历史索引 +memory/gc/ 记 active memory 如何压缩、归档、遗忘 +``` + +### memory/current-status.md + +短文件,Agent 进入项目时优先读。 + +回答: + +```text +现在做到哪? +当前目标是什么? +最近完成了什么? +当前阻塞是什么? +下一步最小动作是什么? +哪些风险不能忽略? +``` + +### memory/phase-dashboard.yaml + +机器可读项目状态。 + +例如: + +```yaml +active_phase: evidence_accumulation +target_venue: CVPR +next_gate: main_table_ready +active_claims: + - CLM-002 + - CLM-004 +open_actions: 12 +high_risks: + - RSK-006 +active_infra_target: gpu_server_a +``` + +### memory/change-control.yaml + +`change-control.yaml` 记录会改变项目方向的变化。 + +它解决的是 goal / scope drift: + +```text +项目从 method paper 变成 benchmark paper; +目标会议从 CVPR 改成 NeurIPS; +主 claim 换了; +baseline set 换了; +主 metric 换了; +dataset split 冻结或重划了。 +``` + +这些变化不能只写在聊天记录里,也不能只改一个文件。每个变化都应该有一个 change record,列出必须同步的文件。 + +示例: + +```yaml +- id: CHG-001 + type: target_venue_change + from: CVPR + to: NeurIPS + decision: DEC-004 + reason: "Experiment timeline no longer fits CVPR deadline." + required_updates: + - PROJECT.md + - memory/phase-dashboard.yaml + - deliverables/paper/ + status: complete +``` + +默认规则: + +- 改 target venue,必须同步 `PROJECT.md` 和 `memory/phase-dashboard.yaml`; +- 改主 claim,必须同步 `lab/research/claims.yaml` 和 `memory/bridge/claim-to-evidence.yaml`; +- 改 baseline set,必须同步 `lab/research/baselines.yaml`、`comparison-matrix.yaml` 和 reviewer risk; +- 改主 metric,必须同步 benchmark protocol、paper table status 和 claim evidence; +- 改 dataset split,必须同步 `lab/data/splits/`、manifest、checksum 和所有受影响实验。 + +### memory/lab/ + +`memory/lab/` 记录实验系统当前推进到哪。 + +它不存实验真相;实验定义、证据和产物仍然属于: + +```text +lab/experiments/ +lab/research/ +lab/artifacts/ +``` + +典型文件包括: + +```text +active-experiments.yaml 哪些实验在跑、哪个 seed、哪个 target +run-queue.yaml 接下来要提交哪些 job +infra-status.yaml 哪些机器可用、哪些 target blocked +data-status.yaml split / manifest / checksum 是否 ready +blockers.yaml 当前阻塞实验推进的问题 +``` + +每个实验状态应该连接到 claim、experiment、risk 或 infra blocker。 + +例如: + +```yaml +- id: ACT-021 + title: "Rerun E004 low-data ablation with 3 seeds" + related_claim: CLM-002 + related_risk: RSK-006 + experiment: E004-low-data-regime + infra_target: cluster_b + status: blocked + blocker: "cluster_b dataset path not validated" +``` + +`infra-status.yaml` 可以记录当前 infra 状态: + +它不替代 `lab/infra/`,而是记录项目推进中的 infra 可用性: + +```yaml +active_target: gpu_server_a +validated_targets: + - local_mac + - gpu_server_a +blocked_targets: + - cluster_b +open_infra_risks: + - RSK-007 +last_probe_report: lab/infra/reports/probe-2026-06-23.md +``` + +### memory/paper/ + +`memory/paper/` 记录文章当前推进到哪。 + +它不存论文正文;论文正文仍然属于: + +```text +deliverables/paper/ +``` + +典型文件包括: + +```text +manuscript-status.md 当前 paper 阶段:outline / draft / internal review / submission +table-status.yaml 每张表是否有 evidence 支撑 +figure-status.yaml 每张图是否有 source artifact +section-status.yaml 每节是否 complete / stale / needs evidence +reviewer-risks.yaml reviewer 可能攻击哪里 +rebuttal-status.yaml 投稿后 rebuttal 状态 +``` + +### memory/bridge/ + +`memory/bridge/` 是最关键的连接层。 + +它记录实验事实如何进入文章: + +```text +claim-to-evidence.yaml 每个 claim 被哪些 evidence 支持 +evidence-to-table.yaml 哪些 evidence 进入了哪张 table +result-to-figure.yaml 哪些 result / sample 进入了哪张 figure +promises.yaml rebuttal 或 camera-ready 承诺 +handoff-log.md 人或 Agent 的交接记录 +``` + +`memory/bridge/` 不应该复制 `lab/research/evidence.yaml` 的全部内容。它只记录投影关系: + +```text +lab/research/evidence.yaml + -> memory/bridge/evidence-to-table.yaml + -> deliverables/paper/tables/ +``` + +### decisions 和 handoffs + +记录关键选择。 + +例如: + +- drop 某个 baseline; +- freeze dataset split; +- 改主 metric; +- 改训练预算; +- 改投稿目标; +- 某台服务器不再跑主实验。 + +每次 Agent 或人结束一个阶段,应该留下: + +- 改了什么; +- 跑了什么; +- 哪些检查通过; +- 哪些检查没跑; +- 哪些假设不能当事实; +- 下一步最小动作。 + +### memory/gc/:记忆遗忘机制 + +`memory/` 不是永久事实库。 + +它是工作记忆、控制面板和当前上下文缓存。长期项目如果只允许增量写入,不允许遗忘,会出现两个问题: + +```text +stale memory poisoning + 过期状态还留在 memory/,Agent 读了以后按旧事实行动。 + +memory overload + 所有历史都堆在 current-status / status boards 里,最后没人知道什么是当前有效信息。 +``` + +所以 `memory/` 必须有遗忘协议。 + +核心原则是: + +```text +遗忘 memory,不等于删除事实。 +``` + +真正的长期事实源应该在: + +```text +lab/research/ claim / evidence / negative result +lab/artifacts/ result / model / sample index +lab/experiments/ experiment definition +deliverables/ paper / rebuttal / release +DECISIONS.md 关键决策 +``` + +`memory/` 只保存“现在要用来推进项目的状态”。遗忘的意思是: + +```text +从 active working memory 中移除; +必要时压缩进长期事实源; +保留最小 tombstone 或 archive; +防止旧状态继续误导 Agent。 +``` + +一个可操作的 memory 生命周期可以是: + +```text +active 当前正在影响行动的记忆 +warm 最近完成,但可能还要回看 +archived 已经不影响当前行动,只保留索引 +forgotten 已被确认无价值,或已被长期事实源吸收 +``` + +`memory/gc/retention-policy.yaml` 规定什么时候应该压缩或遗忘: + +```yaml +rules: + completed_run_queue_items: + after: 14d + action: archive_summary + + resolved_blockers: + after: 7d + action: compact_to_handoff_log + + stale_active_experiments: + after: 30d_without_update + action: require_review + + paper_table_status: + after: table_committed_to_paper + action: keep_pointer_only + + rebuttal_promises: + after: camera_ready_done + action: move_to_archive +``` + +`memory/gc/compaction-log.md` 记录每次压缩: + +```text +2026-06-26 +- Compacted resolved infra blockers from memory/lab/blockers.yaml. +- Durable facts moved to lab/research/negative-results.md and DECISIONS.md. +- Active memory now only keeps unresolved blockers. +``` + +`memory/gc/tombstones.yaml` 防止已经遗忘的事项被反复复活: + +```yaml +- id: ACT-021 + forgotten_at: 2026-06-26 + reason: "experiment rerun completed and evidence recorded" + durable_record: + - lab/research/evidence.yaml#EVD-014 + - lab/artifacts/result-index.yaml#ART-008 +``` + +不同 memory 区域的遗忘规则不同。 + +`memory/lab/` 应该遗忘运行噪音: + +```text +run 已完成 -> result summary 写入 lab/experiments/ +evidence 已登记 -> 从 active-experiments 移除 +blocker 已解决 -> compact 到 handoff-log 或 DECISIONS.md +失败路线有研究意义 -> 移到 lab/research/negative-results.md +``` + +`memory/paper/` 应该遗忘草稿状态: + +```text +section 已稳定 -> 只保留 current status +table 已进论文 -> 只保留 evidence pointer +reviewer risk 已处理 -> archive,不继续污染 active risks +rebuttal promise 已兑现 -> tombstone + archive +``` + +`memory/bridge/` 最不能轻易遗忘,因为它连接实验和文章: + +```text +claim-to-evidence.yaml 尽量长期保留 +evidence-to-table.yaml 至少保留到投稿 / camera-ready 后 +result-to-figure.yaml 至少保留到 artifact release 后 +promises.yaml 承诺完成后 archive + tombstone +handoff-log.md 可定期压缩 +``` + +一句话: + +```text +memory 的遗忘,不是 rm 文件; +而是 active -> compact -> archive/tombstone 的状态迁移。 +``` + +--- + +## 11. 核心链路:从 Claim 到 Paper Table + +这套结构真正的价值在于链路。 + +一个理想状态下的实验证据链应该是: + +```text +CLM-002: 新方法在低数据 regime 下提升 FID + -> lab/research/claims.yaml + -> lab/experiments/E004-low-data/ + -> lab/code/configs/experiment/E004-low-data.yaml + -> lab infra target: gpu_server_a + -> lab/runs/2026-06-23_E004_seed{0,1,2} + -> lab/artifacts/result-index.yaml + -> lab/research/evidence.yaml + -> deliverables/paper/tables/table2.tex +``` + +每一层都有自己的职责: + +| 层 | 负责的问题 | +|---|---| +| `lab/research/claims.yaml` | 为什么要跑 | +| `lab/experiments/` | 怎么定义实验 | +| `lab/code/configs/` | 实验参数是什么 | +| `lab/infra/` | 在哪里跑、怎么跑 | +| `lab/runs/` | 运行时发生了什么 | +| `lab/artifacts/` | 产物在哪里 | +| `lab/research/evidence.yaml` | 结果证明了什么 | +| `deliverables/paper/` | 如何表达给读者 | + +这能防止一个常见问题: + +```text +实验越跑越多,但 claim 越来越不清楚。 +``` + +--- + +## 12. 主工作流 + +一个健康的 ML research repo 应该围绕以下循环运转: + +```text +提出 hypothesis / claim + -> 在 lab/experiments/ 设计 experiment card + -> 绑定 lab/code/configs/ 里的 config + -> 选择 lab/infra/ target + -> 跑 lab/infra/probes/ + -> 提交训练 / 评估 + -> 收集 lab/runs/ logs + -> 登记 lab/artifacts/ + -> 写 result summary + -> 更新 lab/research/evidence.yaml + -> 更新 claim status + -> 通过 memory/bridge/ 投影到 paper table / figure + -> 更新 memory/current-status.md +``` + +如果某一步失败,也要写回: + +```text +failure + -> lab/research/negative-results.md + -> memory/lab/blockers.yaml + -> memory/paper/reviewer-risks.yaml + -> memory/bridge/claim-to-evidence.yaml + -> maybe revise hypothesis +``` + +失败不是噪音。失败是研究图的一部分。 + +--- + +## 13. Human Gate 应该放在哪里 + +ML research 项目里,有些节点不应该让 Agent 自动越过。 + +建议 human-gated 的节点包括: + +- 确认核心 research claim; +- drop / add 关键 baseline; +- 更改 benchmark protocol; +- 更改 dataset split; +- 更改主 metric; +- 采用新数据集或公开数据集; +- 使用大规模计算预算; +- 把 partial evidence 升级为 supported claim; +- 投稿; +- rebuttal strategy; +- artifact release; +- 公开 checkpoint / dataset。 + +这些 gate 应该写进 `AGENTS.md`、`memory/phase-dashboard.yaml` 或 `lab/infra/launch` 流程,而不是每次靠临时提醒。 + +--- + +## 14. 第一版先守住什么 + +第一版不用全量目录,也不需要把所有 board 和 ledger 都一次性填满。 + +但它必须从第一天就守住四个不变量: + +1. **claim/evidence 链路**:每个实验必须知道自己服务哪个 claim; +2. **infra/path 可复现**:每个实验必须知道在哪个 target、哪个环境、哪个逻辑路径下跑; +3. **current-status 可接续**:任何人或 Agent 三天后回来都知道下一步是什么; +4. **memory 可遗忘**:active memory 不能无限增长,完成、过期、失效的状态必须被 compact / archive / tombstone。 + +--- + +## 15. 防漂移机制:Research Harness Drift Control + +上面的结构如果只停留在文档里,长期开发后一定会漂移。 + +典型漂移包括: + +- 新增了实验目录,但没有绑定 claim; +- 论文表格出现了数字,但 `lab/research/evidence.yaml` 没有对应 evidence; +- result index 记录了 artifact,但没有 source config / commit; +- 某个 config 写死了服务器绝对路径; +- 引入了 `SOME_LOCAL_PACKAGE` 这种本地不可复现依赖; +- launch script 依赖 `/Users/.../private_script.sh`; +- 项目目标从 method paper 变成 benchmark paper,但 `PROJECT.md`、claims、paper 和 memory 没同步; +- baseline 复现状态变了,但 reviewer risk 没更新; +- `memory/current-status.md` 过期,Agent 接手时只能靠聊天记录猜下一步; +- private overlay、真实路径或敏感信息被误提交。 + +所以这套 repo 结构需要一个 drift control 闭环。 + +可以把 drift 分成四类: + +```text +structure drift 仓库结构被写歪 +dependency drift 依赖、脚本、外部工具不可复现 +goal / scope drift 项目目标变化但没有同步事实源 +memory drift active memory 过期、膨胀、指向旧事实 +``` + +### 15.1 Structure Drift + +Structure drift 是最容易机械化检查的。 + +例如: + +```text +lab/ 被打散; +experiments/ 又被放回 repo 根目录; +memory/ 变成杂乱 todo; +deliverables/ 开始存实验事实; +lab/experiments/E###-* 缺 experiment-card.md 或 linked-claims.yaml。 +``` + +对应守护: + +```text +repo structure contract +scripts/check-research-harness.py +pre-commit / CI gate +``` + +validator 至少应该检查: + +```text +required dirs; +forbidden root dirs; +required memory/gc files; +experiment contract files; +artifact/evidence links; +paper table 是否能回指 evidence。 +``` + +### 15.2 Dependency / Script Drift + +Dependency drift 是 ML research repo 里最危险的隐性漂移之一。 + +危险情况包括: + +```text +pip install -e ../SOME_LOCAL_PACKAGE +from SOME_LOCAL_PACKAGE import * +shell script depends on /Users/foo/private_script.sh +training command assumes a local binary +external repo cloned but commit unknown +curl ... | bash +``` + +对应守护: + +```text +lab/infra/dependencies.yaml +lab/infra/external-scripts.yaml +lab/infra/private/ +lab/infra/vendor/ # 可选,用于放入允许 vendor 的外部脚本或小依赖 +``` + +默认规则: + +- 任何非标准依赖都必须登记来源、版本、commit、license、用途、是否可替代; +- 任何本地路径依赖默认禁止进入主实验链路; +- 如果必须用本地路径,只能放在 `lab/infra/private/` overlay,并标记 non-reproducible; +- 任何外部脚本必须 pinned 到 commit 或 hash; +- 任何 `curl | bash`、裸 `/Users/`、裸 `/home/`、裸 `/mnt/` 都应该触发检查; +- release 前必须消除 private dependency,或提供公开替代路径。 + +validator 可以检查: + +```text +pyproject.toml / requirements.txt / uv.lock / conda yaml 中是否有 ../ 或 file://; +scripts 里是否出现 /Users/、/home/、/mnt/ 这类裸路径; +import SOME_LOCAL_PACKAGE 是否没有出现在 dependencies.yaml; +shell 脚本是否出现 curl | bash; +external-scripts.yaml 里的 source 是否有 pinned_ref 或 sha256。 +``` + +### 15.3 Goal / Scope Drift + +Goal drift 是项目中期最常见的研究漂移。 + +例如: + +```text +从 diffusion method 变成 benchmark paper; +从 CVPR 改成 NeurIPS; +主 claim 换了; +baseline set 换了; +metric 换了; +dataset split 重划了。 +``` + +这些变化如果只发生在聊天记录里,Agent 三天后接手时会继续按旧目标工作。 + +对应守护: + +```text +DECISIONS.md +PROJECT.md +memory/change-control.yaml +memory/phase-dashboard.yaml +lab/research/claims.yaml +memory/bridge/ +``` + +Goal Change Protocol: + +```text +任何目标变化 -> DECISIONS.md entry +任何 active_phase / target_venue / main_claim 改动 -> PROJECT.md + memory/phase-dashboard.yaml +任何主 claim 改动 -> lab/research/claims.yaml + memory/bridge/claim-to-evidence.yaml +任何 baseline / metric / split 改动 -> research ledger + paper status + reviewer risk +``` + +`memory/change-control.yaml` 是同步清单,不是事实源。它的作用是防止“改了 A,忘了 B/C/D”。 + +### 15.4 Memory Drift + +Memory drift 最隐蔽。 + +例如: + +```text +current-status.md 说 E004 blocked,但其实已经完成; +memory/paper/table-status.yaml 说 table2 needs evidence,但 evidence 已经登记; +memory/bridge/ 还指向旧 claim; +resolved blocker 继续留在 active blockers; +active-experiments 里有一个 30 天没更新的 running experiment。 +``` + +对应守护: + +```text +memory/gc/retention-policy.yaml +memory/gc/compaction-log.md +memory/gc/tombstones.yaml +scripts/check-research-harness.py +research-repo-auditor skill +``` + +validator 应该检查: + +```text +memory/current-status.md 是否太久没更新; +memory 中引用的 CLM/EVD/EXP 是否存在; +active-experiments 里 completed 项是否超过 retention policy; +resolved blocker 是否还留在 active blockers; +memory/bridge 是否指向已经不存在的 claim / evidence。 +``` + +auditor skill 定期检查语义一致性: + +```text +PROJECT.md 当前目标是否和 claims / paper / memory 一致; +memory/bridge 是否还指向有效 evidence; +paper table 是否引用了不存在或 stale 的 evidence; +change-control 是否有未完成同步项。 +``` + +### 15.5 确定性 validator + +仓库应提供一个脚本,例如: + +```text +scripts/check-research-harness.py +``` + +它进入 pre-commit 或 CI,负责检查那些可以机械验证的事情: + +```text +structure: + 必需目录和入口文件存在; + 禁止实验系统目录漂到 repo 根目录 + +schema: + claims / evidence / experiment-ledger / result-index / change-control 可解析 + +referential integrity: + experiment 引用的 claim 存在; + evidence 引用的 experiment / artifact 存在; + artifact 支持的 evidence 存在; + memory 引用的 claim / evidence / experiment 存在 + +experiment contract: + 每个 lab/experiments/E###-* 都有 experiment-card.md、config.yaml、linked-claims.yaml + 每个实验至少绑定一个 CLM-* 或 HYP-* + +infra contract: + config 和 ledger 不写裸绝对路径; + lab/infra/private、lab/runs、*.log 被 gitignore; + dependencies.yaml 登记非标准依赖; + external-scripts.yaml pin 外部脚本 + +memory freshness: + memory/current-status.md 写明当前目标、阻塞/风险、下一步; + memory/gc 文件存在; + stale active memory 被标记 review 或 compact + +privacy: + 不提交 token、password、private key、真实私有 overlay +``` + +这个 validator 不判断研究是否正确。它只回答: + +```text +这个 repo 的证据链有没有机械断裂? +``` + +本学习仓库已经给出一个可复用起点: + +```text +scripts/check-research-harness.py +``` + +未来创建 ML research repo 时,可以把它复制进去,并在 CI 里运行: + +```bash +python3 scripts/check-research-harness.py +``` + +### 15.6 语义审核 Skill + +还有一些问题不能只靠脚本判断: + +- 实验是否真的支持它声称支持的 claim; +- partial evidence 是否被过早升级成 supported claim; +- baseline comparison 是否公平; +- negative result 是否改变了论文叙事; +- reviewer risk 是否有 action 承接; +- paper table 是否选择性展示样本或数字; +- PROJECT.md、claims、paper、memory 是否仍然描述同一个目标。 + +这些应该交给一个专门的审核 skill: + +```text +skills/research-repo-auditor/SKILL.md +``` + +它的职责不是重写项目结构,而是定期做 harness audit: + +```text +mechanical validator output + -> semantic audit + -> ranked findings + -> required fixes before submission / release / next large experiment +``` + +理想输出不是泛泛建议,而是: + +```text +Findings +- [High] deliverables/paper/tables/table2.tex 引用了 3 个数字,但只有 1 个能回溯到 EVD-*。 +- [High] E004 声称支持 CLM-002,但 linked config 使用了不同 data split。 +- [Medium] BASE-004 使用 official checkpoint,BASE-001 是重新训练,comparison-matrix 没声明 checkpoint source 差异。 + +Validation +- python3 scripts/check-research-harness.py: failed, 4 structural defects. + +Residual Risk +- 没有重新跑实验,只审核了 repo 中已有证据链。 +``` + +换句话说: + +```text +bootstrap skill 负责出生; +validator 守结构、依赖、路径、引用和 memory freshness; +auditor skill 守研究语义、目标同步和 harness 纪律; +memory GC 负责清理 stale active memory; +change-control 负责管理目标变化和必要同步。 +``` + +前者适合每次 commit 跑,后者适合这些节点跑: + +- 新增一批实验后; +- 主要表格进论文前; +- claim 从 partial 升级 supported 前; +- rebuttal 前; +- artifact release 前; +- 长时间多 Agent 开发后。 + +--- + +## 16. 结论 + +一个经典 ML research project 的 repo,不应该只服务“写代码”和“写论文”。 + +它应该同时服务五件事: + +```text +实现方法; +运行实验; +管理基础设施; +积累证据; +表达成果。 +``` + +因此,`paper/`、`reviews/` 这类目录不应该和 `src/`、`experiments/` 平铺成同级概念。它们是 human-facing deliverables。 + +实验、baseline、benchmark runner、训练脚本、核心算法,应该属于 `lab/code/`。 + +服务器、路径、conda/uv/Docker、scheduler、storage、probe,应该属于 `lab/infra/`。 + +研究主张、证据、负结果、comparison matrix,应该属于 `lab/research/`。 + +实验推进状态、文章推进状态、实验到文章的桥接关系,应该属于 `memory/`。 + +这套结构的核心价值可以概括为: + +> 把 ML research 从一堆脚本、日志和论文草稿,变成一个 claim-driven、infra-aware、可复现、可接续的研究系统。