Virtual Embryo Challenge更新于 10-03 20:13(北京时间) / 每 5 分钟更新

← 工作原理 · 原文件 notes/architecture/15_data_agent_compliance.md(Markdown 源文件已渲染;链接到其他文档的会跳转,指向源码的只显示路径)

15. Compliance 角色、Data agent 与 vec-data 数据服务(G52)

一句话:Researcher 可以查数据集元数据并在 PLAN 里提数据请求;Data agent 在 Engineer 之前按请求下载,服务先按元数据删掉本任务禁窗内的样本再下载、下载后在隔离区逐文件 / 逐细胞再删一次、算哈希、写 catalog;Compliance 用正则 + 单轮 LLM 审 PLAN、检索文本、数据元数据和用途说明;通过的数据挂进本 run 各视图的 external/dynamic/<id>/,并永久存入 data/external/<id>/ 供以后的 run 直接挂载。节点记录数据纪元 data_epoch,分数只在同一纪元内比较。

模块、任务书、单元测试已完成;与 controller.py 的接线没有做(另一个 agent 正在改主流程),接线点见 §7。下载只能在 Spark 上跑(VM 到 NCBI 的 TLS 不通),本文所有代码在 VM 上只用假 HTTP 测过(§9)。

1. 流程

Researcher(单轮)  lit-search + vec-data search(只读元数据)  → PLAN.json(可带 data_requests)
      │
Compliance(正则 + 单轮 LLM)审 PLAN 与 lit-search 返回文本 → 删测量值句子、标 [compliance: removed]
      │      主要段落过半被删 → 退回 Researcher 重写一次
      ▼
Data agent(多轮,仅当 data_requests 非空)  vec-data fetch → 服务:元数据删窗 → 下载到隔离区 → 删窗 / 删细胞
      │                                       → 哈希 → 摘要;agent 读摘要写 DATA.json
Compliance(确定性检查 + 用途文本审查)→ admit:移入 data/external/<id>/、写 catalog_auto.yaml、
      │                                       挂到本 run 各视图 external/dynamic/<id>/、数据纪元 +1
      ▼                                 reject:隔离区删除,不挂载
Engineer(可再 vec-data fetch --purpose,同样过 Compliance,由后台审查线程处理)
      ▼
执行(节点记 data_epoch)→ Analyst → Reviewer(看得到 PLAN 里的 [compliance: removed] 与 _compliance 说明)

2. 文件

文件内容
agent/data/staging.py元数据 → 阶段区间:E 日 / dpc / embryonic day / Qiu E0850 / 体节(Qiu 2024 表)/ Theiler(eMouseAtlas 表)/ 出生后;多个标记取并集(保守)。judge_sample:无阶段拒绝、碰本任务禁窗拒绝、保留基因型与其他扰动拒绝;主办方例外 ORGANIZER_EXCEPTIONS(GSE208162 仅 T3)
agent/data/repos.py仓库元数据:GEO(E-utilities 检索 + acc.cgi SOFT 全样本)、CELLxGENE(curation API)、ArrayExpress(BioStudies,只读元数据)、figshare(API v2)、CNGB(关闭,见 §8);Downloader:只 https、只放行域名、重定向只到放行域名或 figshare 的 S3
agent/data/store.pyplan_fetch(只看元数据决定保留哪些样本、下载哪些文件、怎样过滤)、process(tar 只解出保留样本的成员;h5ad 按 obs 阶段列与基因型列逐细胞删除;文件名再查一遍;原始下载删除)、summarize(细胞数、基因数、与本榜面板交集、坐标)、admit(移入永久库、写 catalog_auto.yaml,文件锁 + 原子替换)
agent/data/service.py宿主机守护进程(每 run 一个):客户端 socket data-<client>.sock(沙箱内 /run/vec/data.sock),控制 socket data-control.sock;后台下载任务;data_log.jsonl、data_epoch.json
agent/data/client.py沙箱客户端 vec-data(只用标准库):search / fetch / status [--wait] / submit
agent/data/config.py配置块 data: 的规范化、data.yaml、把 vec-data 装进沙箱工具目录
agent/search/compliance.py正则表(FLAG_TABLE、flags_in)、句子切分、LLM 提示块与判定解析、删句、review_plan / review_lit / review_data、DataReviewWorker、controller_llm 适配
agent/search/data_role.py控制器侧胶水:启动服务、Researcher 的 socket 与工具策略、compliance_plan、run_data_agent、engineer_data、start_review_worker、current_epoch
agent/search/dataview.py(新增函数)mount_dynamic / mount_dynamic_all(本 run 挂载)、link_auto_admitted / auto_admitted_external(以后的 run 挂载);build_search_view 未改
agent/search/roles.py(新增)工具策略 single_turn_research(lit-search + vec-data search)、single_turn_datasearch;validate_data_requests(validate_plan 末尾调用)
agent/search/archive.py(新增列)data_epoch INTEGER
agent/search/fake_llm.py(新增角色)compliance、data 的假实现(测试与假 run 用)
agent/prompts/compliance_spec.md、data_spec.md两个新角色的任务书;researcher_spec.md 只追加“数据请求”一节
agent/data/test_data.py、agent/search/tests/test_compliance.py单元测试(§9)

3. vec-data 服务

协议(与 lit-search 相同的分帧:一次连接一个 JSON 请求、一个 JSON 回答):

操作谁能用做什么
search {query, repos?, max_results?}所有客户端关键词检索(GEO / CELLxGENE / ArrayExpress / figshare),只回元数据,附 stage_hint
search {accession}所有客户端一条记录:样本(元数据前 12 条、阶段区间与换算、基因型判定、本 run 是否允许)、文件与大小(未知时 HEAD 补,最多 40 个)、许可、本 run 能下载哪些文件
fetch {accession, keep_stages?, purpose?}有下载权的客户端(Data agent、Engineer)先做 plan_fetch;拒绝则立即返回原因;否则起后台任务,立即返回 qid 与计划
submit {qid, purpose}任务所属客户端隔离区任务申请入库(进入 awaiting_compliance)
status {qid?}任务所属客户端任务状态、保留 / 删除清单、摘要、当前纪元
控制:add_client {client_id, allow_fetch, node, role}、remove_client、items {client_id?, state?}、decide {qid, verdict, compliance, data_note}、epoch、ping、shutdown控制器decide admit = 入库 + 写 catalog + 挂载 + 纪元 +1

任务状态:planned → downloading → quarantined →(带 purpose)awaiting_compliance → admitted | rejected;refused(元数据阶段就被拒:没有阶段信息、没有允许的样本、整包文件无法拆分);failed(下载 / 过滤出错,隔离区删除)。

放行域名(用户 2026-10-03):ncbi.nlm.nih.gov(含 eutils / www / ftp 子域)、cellxgene.cziscience.com(含 api / datasets 子域)、figshare.com(含 api / ndownloader)、db.cngb.org;元数据另读 www.ebi.ac.uk(ArrayExpress,不下载)。figshare 下载会 302 到 S3(REDIRECT_HOSTS),只接受作为重定向目标。不设下载额度,每次调用、每个文件(URL、字节数、sha256、耗时)写 data_log.jsonl;只有磁盘保护:单文件上限 max_file_gb(60)、剩余空间下限 min_free_gb(50)。

存储布局(克隆根目录,任何沙箱之外):

data/external/_quarantine/<qid>/raw/    下载(process 结束即删)
data/external/_quarantine/<qid>/kept/   过滤后等待 Compliance(admit 时移走,reject 时删除)
data/external/<dataset id>/             入库文件;id = auto_<accession>_<内容哈希 8 位>,同内容重复入库复用
data/external/catalog_auto.yaml         自动入库条目

catalog.yaml 由 curate_external.py 生成并进 git,run 内写它会让工作树变脏、下次生成会覆盖,所以自动条目单独放 catalog_auto.yaml(不进 git,条目字段沿用 catalog 的 dataset 结构):id、use: train、status: auto_admitted、species、source{repo, accession, title, url, doi, pubmed}、license{name, verified}、admitted{time, run, node, role, client}、setting、keep_stages、staging[每个样本的区间 / 换算 / 基因型 / 去留理由]、derived[path, sha256, bytes, e_lo, e_hi, stages, allowed_settings, admitted_for, source_file, source_sha256, cell_filter 摘要, n_cells, n_genes, panel_intersection, coordinates]、removed[删了什么:名称、理由、哈希、阶段(metadata / tar / cells / name)]、compliance、purpose、disclosure(一行披露文本)。check_catalog 目前不读它(§8)。

4. 删窗规则(不信任请求方)

按本 run 的任务(windows.board_to_setting):T1 (9.5, 13.5](E10.5 / E12.5 在内);T2 心脏 (8.25, 8.75)、(9.5, 13.5](E8.5 / E10.5 / E12.5 在内);T2 全胚 (7.25, 8.0)(E7.5 / E7.75 在内);T3 无阶段窗,保留基因型。体节、Theiler 先换算再判断(换算表在 windows.py,宽区间、保守)。

  1. 下载前(元数据):每个样本的全部元数据文字取阶段并集;没有阶段 → 删;碰窗 → 删;保留基因型(Gata4 / Ctnnb1 / β-catenin 加扰动词)或表型拷贝(XAV939、IWR-1、Dkk1 过表达、Wnt 抑制)→ 删;其他扰动(KO、Cre、flox、morpholino…)且没写野生型 → 删(训练数据只用野生型,沿用 catalog 的 genotype_rule)。主办方例外只解除基因型规则、只对列出的任务。--keep-stages 只能再缩小。整个记录没有一个样本有阶段信息 → 拒绝。
  2. 选文件:样本级文件只下保留样本的;GEO 的 _RAW.tar 下载但只解出保留样本前缀(GSM…_)的成员;h5ad(CELLxGENE 多阶段文件、figshare 对象)下载后逐细胞过滤;其余混合了禁窗样本的整包文件不下载。
  3. 下载后:h5ad 的 obs 里所有像阶段的列(development_stage、stage、day、age、timepoint、somite、Theiler…)逐值判定,碰窗、无法解析的值所在细胞全部删除;基因型列同理;没有可解析阶段列的文件只有在元数据给出单一允许区间时整体保留,否则删除。保留文件的文件名再查一次阶段与基因型。原始下载随后删除。
  4. Compliance 复核(compliance.data_checks):任务 setting 一致、物种是小鼠、每个保留样本(或保留的阶段标签)重新判定仍允许、保留文件名无禁窗标记、有用途说明。任何一条不过 → 不挂载、隔离区删除。

与 2026-10-02 Spark 规则的张力:data/external/SPARK_EXCLUDE 写着“任务禁窗的数据不能落在 Spark 上”。本设计能在元数据阶段拆开的(样本级文件)从不下载禁窗样本;但 GEO _RAW.tar 和多阶段 h5ad 必须整包下载到隔离区再删,禁窗字节会在 Spark 磁盘上短暂存在(几分钟到几小时,完成即删,data_log.jsonl 有记录)。用户 10-03 的定稿(“下载 → 隔离区 → 删窗”)接受了这一点,但与 10-02 的规则字面冲突,请用户确认;若不接受,配置 data.post_filter: false:含禁窗样本 / 细胞的文件(_RAW.tar、多阶段 h5ad)一律不下载,只取样本级文件(已实现,有单元测试)。

已定(2026-10-03 集成):用户 grilling Q9 / Q18 的决定是“下载不违规、使用才违规”,data.post_filter 默认 true;SPARK_EXCLUDE 与 11 号文 §3 已按此改写,decisions.tsv 记一行。

5. 挂载与跨 run 复用

  • 本 run(动态):decide admit 后服务调用 dataview.mount_dynamic_all:对 views/ 下每个视图(test/ 除外),按与 allowed_external 相同的规则选文件——文件区间必须在本任务允许范围(allowed_settings),proxy 型视图再按 proxy_blocked 相对它自己的输入 / 目标过滤,proxy2 一律不挂。文件硬链接到 <run_dir>/inputs/dynamic/<id>/(跨文件系统时复制),视图里建符号链接 external/dynamic/<id>/<file> 指向它;manifest.json(以及中性视图的 harness 旁注)的 external 列表追加 {id, path, stages, e_lo, e_hi, sha256, n_cells, n_genes, origin: "dynamic", data_epoch, note},note 是披露文本。
  • 为什么链接指向 run 目录:Engineer 会话启动时只绑定了当时视图里链接的目标(sandbox.view_ro_paths),会话中途新增的链接指向别处会悬空。接线时把 <run_dir>/inputs/dynamic/ 整个只读绑定进 Engineer(目录绑定能看到后来新增的文件);执行候选程序时每次重新计算绑定,无需特殊处理。pin_view_links 跳过 run 目录内的目标,所以不重复钉住。
  • 以后的 run:build_run_views 在 build_search_view 之后调用 dataview.link_auto_admitted(view, board, mode),把 catalog_auto.yaml 里本任务允许的文件链接到 external/<id>/(origin auto_admitted,带 note),随后 pin_view_links 照常钉住。这会改变视图内容(锁里的视图记录与评分指纹随之不同),所以要配置开关(建议 data.mount_auto_admitted,默认随 data.enabled)。

6. 数据纪元(data_epoch)

  • <run_dir>/data_epoch.json:{epoch, mounts: [{epoch, id, time, views}]},每次至少挂上一个文件的 admit 加 1;data_role.current_epoch(run_dir) 读它。
  • 节点在开始执行时记录当时的纪元(archive 新列 data_epoch;没有 vec-data 的 run 为 NULL = 0)。理由:程序读的是执行时视图里的数据。
  • 比较规则(留给 G39 的选择 / 终选集成,本任务不改排序逻辑):
    1. 父节点选择与晋升:只在同一纪元内比较分数;跨纪元比较时以较新纪元的节点为准,或把旧纪元的高分节点在新纪元重跑一次种子 0 再比较。
    2. 终选:候选在最终纪元重跑(终选本来就重跑 3 个种子;要求终选开始后不再挂载新数据——终选阶段停止审查线程、移除所有有下载权的客户端即可),SELECTION 记录最终纪元号。
    3. 只有读了 external/dynamic/ 的程序分数才真正受影响;集成时可以用 viewcheck 的静态扫描或执行时的文件访问记录区分,作为优化,不是必须。

7. 与 controller.py 的接线点清单(集成时做;不改已有函数签名)

#位置(controller.py 现有函数)插入什么
W1load_search_configcfg["data"] = agent.data.config.normalize(cfg.get("data"));角色循环加 "compliance"、"data"(缺省回落到 reviewer / engineer 的模型;fake 时用 fake_llm 新角色);prompts_by_role 加 compliance: agent/prompts/compliance_spec.md、data: agent/prompts/data_spec.md;compliance_minutes 从 data 块取
W2代码快照清单("agent/research/*.py" 那一行,约第 115 行)加 "agent/data/*.py";锁里记录 data 配置与 fetch_domains
W3SearchServices.startif cfg["data"]["enabled"]: data_role.start_data_service(self.run_dir, self.cfg, self.procs)(失败 → self.stop(); raise SearchError);kill_stale_daemons 的 ready 文件加 data.ready;DAEMON_LOGS 加 data_log.jsonl、data_service.log、data_epoch.json;_services_and_run 的配置文件清单加 data.yaml
W4runenv.RunEnv.prepare_tools(runenv.py,另一 agent 在改)agent.data.config.install_tool(self.tools)
W5build_run_views每个视图 build_search_view 之后(开关开时)dataview.link_auto_admitted(views / mode, cfg["board"], mode);建 <run_dir>/inputs/dynamic/
W6Controller.researchlit socket 准备好之后:sockets, policy, close_data, data_lines = data_role.researcher_tools(self, nid, sockets),用返回的 policy 替换 single_turn_lit / single_turn 的选择,data_lines 追加到 researcher_block 的结果;finally 里 close_data()
W7Controller.research,PLAN 校验通过、return plan 之前plan, cres = data_role.compliance_plan(self, nid, plan, cid if 用了 lit else None);cres["status"] == "rewrite" 且尚未因合规重写过 → feedback = cres["feedback"] 再调一次 Researcher(独立计数,不占 JSON 无效的那次重试);第二次仍 rewrite → 用删过的 PLAN(approach 为空则 gen_failed);info["compliance"] = cres
W8Controller.process_node,self.put(ndir / "PLAN.json", …) 之后、self.engineer(nid) 之前if data_role.needs_data_agent(self, plan): dinfo = data_role.run_data_agent(self, nid, plan);calls["data"] = dinfo["calls"]、calls["data_jobs"] = dinfo["jobs"]、轨迹加 dinfo["stream"];PLAN 加 _data(入库的 id、视图路径、披露文本)再写一次 PLAN.json 与 ar.update(plan=…)。Data agent 失败不让节点失败(Engineer 照常,无新数据)
W9Controller._engineerdsocks, dro, dlines, dclose = data_role.engineer_data(self, nid);sockets += dsocks、ro += dro、engineer_block 结果追加 dlines;finally: dclose()
W10Controller.run(或 prepare)开始 / 结束;终选开始时self.data_worker = data_role.start_review_worker(self);终选开始与 run 结束时 self.data_worker.stop()(终选阶段不再挂载,见 §6)
W11process_node 中 self.ar.update(nid, status="executing", t_exec_start=now())同一调用加 data_epoch=data_role.current_epoch(self.run_dir);晋升重跑(seed 1、2)与 rescore_node 同样记录(写进 seed_scores 的每个种子)
W12select.py / final.py(G39 的 agent)按 §6 的比较规则使用 data_epoch;SELECTION.md 的数据披露段列出视图 manifest 里 origin 为 dynamic / auto_admitted 的条目及其 note
W13review_spec.md(另一 agent 在改)加一句:PLAN 中的 [compliance: removed] 与 _compliance 表示删过测量值;METHOD.md 或代码重新引入同类内容即判 fail
W14scripts/test_on_spark.sh加 agent.data.test_data、agent.search.tests.test_compliance

8. 未完成与待核

  1. 控制器接线(§7,全部未做)。
  2. Spark 上真实 API 未验证:GEO acc.cgi SOFT(targ=all)的字段名、CELLxGENE curation API /datasets 与 /collections/<id> 的字段(development_stage、assets[].filesize)、figshare 下载的 302 目标主机、ArrayExpress SDRF 路径;解析器按文档与记忆写成,单元测试用的是同形状的假数据。
  3. CNGB 关闭:db.cngb.org 已在放行域名里,但没有验证过它的元数据 API,search / describe 返回 disabled。
  4. Spark 规则的张力(§4 末):需要用户确认默认值;严格读法用 data.post_filter: false。
  5. Scout 模式(run 外每日跑、Opus 判定后自动挂载)没有做:可直接复用 repos / store / compliance,缺一个调度脚本。
  6. check_catalog.py 不读 catalog_auto.yaml(auto 条目与测试题目标阶段的重叠检查需要补;目前只靠删窗规则,不防“训练集与外部测试题目标重叠”)。
  7. 大文件:h5ad 过滤读 backed 再 to_memory(),几 GB 的 CELLxGENE 文件内存会很高;max_file_gb 默认 60 只防磁盘不防内存。
  8. opencode 的 bash 工具有命令超时,所以下载做成后台任务、status --wait 建议 ≤ 100 秒;Data agent 会话结束后控制器最多再等 job_wait_minutes(60)。
  9. 许可只记录不判定(GEO 无明确许可,记 verified: false,披露时注明)。

9. 测试(VM,假 HTTP,无网络)

~/.venvs/vec/bin/python -m unittest agent.data.test_data agent.search.tests.test_compliance

覆盖:阶段解析的各种写法与换算;四个任务的禁窗判定(含 E9.5 / E13.5 边界、Theiler 宽区间);基因型规则与 GSE208162 仅 T3 例外;GEO / CELLxGENE / figshare 解析;plan_fetch(GSE278603 形状:E7.5 / E7.75 四个样本的文件不下载,_RAW.tar 只解 E8.0 成员;无阶段拒绝;无法拆分的整包文件拒绝);隔离区处理(tar 成员、h5ad 逐细胞、文件名复查、原始下载删除);socket 往返(Researcher 只能查不能下、后台任务、items / decide、入库、catalog_auto.yaml、final / proxy 挂载而 proxy2 不挂、manifest 行与纪元、data_log.jsonl、拒绝时删除);以后的 run 用 link_auto_admitted 挂载;run_data_agent / researcher_tools / engineer_data / compliance_plan 用替身控制器跑通;Compliance 的正则表、LLM 判定解析、无 LLM 时保守删除、重写规则、lit_copy、数据复核与审查线程;新工具策略、data_requests 校验、archive 新列、fake_llm 的 compliance 角色。