Repository navigation
Freeze native evaluation conditions for result attribution - #63
loopx-agent wants to merge 3 commits into
Conversation
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=max
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
未发现 blocking finding。复核范围是 main 的 ba13e33 到 671e8b7,包含三个提交和全部十个文件。
动机 / Motivation
在独立反馈读取器中消费原生评分的使用者会遇到这个问题。同一运行登记后,服务配置或镜像标签发生变化,旧 token 可能得到另一个评测器的评分,读取器却仍按自己安装的版本解释它。独立真实 Docker 对照已复现这个失配:同一候选原本得到11,登记后改变服务环境,旧 main 的原 token 得到111;本 head 保持11。
现在读取器可以核对服务登记时冻结的条件和实际接受的归档,再解释该结果。普通运行自动获得只读反馈地址,不需要把提交或模型网关权限交给读取方。本 PR 交付现代 TOML/Blob harness 的来源协议与普通入口接入;模型收益、checkpoint/restore 和其他开放提案的组合验收不在本次结论中。
改动思路 / Approach
决定保留这个边界的原因是:来源必须由实际评分服务产生,并且约束后续执行;只增加调用方版本标签,或只给返回值附上当时的文件摘要,都不能阻止原 token 在配置漂移后换评测器。JudgeState 在登记时复制实际 TaskSpec 和有效配置,固定 backend 与 Docker 内容 ID;评分复用这个对象,结果记录同一上下文。既有 select_best 继续负责评分选择,没有增加另一套评分或调度决策。
当前 PR 的边界是登记冻结、已接受上传 receipt、只读 context/result,以及 run_agent 自动传递反馈 URL。资源选项从原评分路径提取为共用 helper;普通结果与 relay 共用正文裁剪和 receipt formatter。源摘要只在服务启动时扫描,context/result 读取不重查镜像、构建镜像或下载 Blob。未知条件保留未知,原生反馈继续可用。
| 实际触发 | 独立验证的结果 |
|---|---|
| 同一普通上传、省略 receipt | base/head 的默认 submit/result 正文相同,评分11 |
| 登记后改变服务环境 | base 原 token 评分111;head 原 token 仍为11、上下文不变 |
| 改 TOML、Blob、grader、方向、资源并重新打 tag | 旧 token 用旧镜像得到10;新 run 用新条件得到119 |
| 同一已知 run/task 漂移后重新登记 | 返回409;新 run 可以继续实际评分 |
具体改动 / Changes
兼容基准是本次改动之前的 Judge HTTP API:spec_ref: docs/en/reference/judge-api.md;spec_revision: ba13e33。原文没有编号,以下标识对应其已有章节:API-REGISTER(登记、独立计次及资源选项)、API-SUBMIT(异步提交、预算及冷却)、API-RESULT(状态和仅原始分数正文)、API-HISTORY(Agent/auto 可见范围及方向)、API-GATEWAY(原 session token 与上游凭据隔离)均在本 head 实现并验证。新增条件冻结按本提案的问题与实际反例判断,没有把这次新写的协议文档当成独立验收证明。
关键代码讲解
- capture_conditions 使用 loader 实际解析的 TOML 字节摘要、有效 TaskSpec/Blob 清单和服务源码摘要,并解析实际 Docker image ID。有效任务被改动、来源未观察、镜像不存在或 backend 不支持时返回未知,不用调用方声明补齐事实。TaskSpec 的镜像内容键仍沿用原 owner。
- JudgeState.register_session 在登记锁内准备并发布 evaluator。已知条件和选项完全相同的重试返回原 token,保持轮次、预算、冷却和 gateway;漂移返回409,不覆盖旧登记。实际并发登记和不同归档已验证。
- JudgeState._grade_worker 从已接受工作携带的 evaluator 取 task/config/backend,并把内容寻址 image ID 传给原评分函数。真实容器 inspect 核对了 Image、CPU、内存和网络配置;旧 token 的实际评分没有跟随服务 reload 或镜像 retag 改变。
- public_result 统一移除 score_0_100,并只在显式 include_receipt 时附带原归档字节摘要、大小及上下文。relay 进一步检查 run/task、已记录的 registration 和 Agent round;外部 run、auto 及未知 ID 返回404。缺登记绑定的旧结果只按匹配 run/task 可见,仍没有恢复出来的归因。
- run_agent 保留 session token 和模型 gateway 路由,另外把只读 URL 放入工作容器。生产入口的无模型 probe 真正安装并执行原生成的 Linux 提交客户端,读取上下文、上传、获得评分,再核对接受的归档;不只是手工调用新 endpoint。
pyproject 显式声明直接依赖 pydantic>=2.0,匹配这些模型实际使用的 ConfigDict/model_dump。双语 API 文档同时说明了默认响应、409、未知条件、legacy 范围和非封闭评分的限制。相关开放提案 #61 的上传保留,以及 #26 的生命周期/公平调度没有被混入本次变更或验收。
对主干的风险 / Risks
include_receipt 默认 false 的隔离经过实际 base/head HTTP 与 Docker 对照,归档摘要覆盖接受的压缩上传字节,而非过滤后的工作区。额外严格 v1 reader 实际只读一次 context、每个结果一次 GET;relay 不能用于 submit、history 或模型 gateway,新增来源字段只发布摘要和标识,未序列化服务端原始环境、source 路径或提交/模型凭据;原生 report 沿用既有内容边界,score_0_100 统一裁剪。非法 kind 在既有 HTTP guard 返回400且不消耗轮次,正常预算耗尽仍为429。
登记冻结和已知重试幂等是有意改变的默认语义,不受 receipt 开关控制:运维不能再靠重登记或 retag 静默替换旧运行。文档明确要求漂移时使用新 run_id,实际新 run 评分证明了恢复到工作的路径。未知镜像、手工 TaskSpec、不支持的 backend、游戏及旧缺失来源仍不能获得已验证归因;旧结果不会套用当前条件。Docker ID 也不证明外部服务确定性或数据封闭性。
PASS:复用 75e787f 的完整48通过、1项可选 provider 跳过(含两条独立补充检查),以及 base/head 配置漂移和非法 kind 真实入口证据。逐文件核对确认最终提交只增加 Pydantic 依赖下限,sforge、docs、tests 字节未变;原测试已运行在 Pydantic 2。最终 head 另跑协议23通过,并完成旧环境普通升级与真实 HTTP 检查。PASS:未变文档的 VitePress build、最终 diff、三个提交 DCO 和公开边界扫描。测试只用临时 synthetic 任务与自有容器,没有模型 API 调用。生产入口 fixture 将机器级 iptables 清理设为 no-op,因此不宣称验证了该未改动的全局清理。
发布前还实际发现并复现了一项安装缺口:先前声明允许 FastAPI 0.99.1/Pydantic 1,注册会因没有 model_dump 返回500。最终提交用一行直接依赖修复;在同一个旧环境中只执行普通安装升级,解析器自动升级到兼容版本,真实 register/context 均为200且没有额外 model_config 字段;强制旧依赖组合会在安装阶段明确拒绝。该500记录保留,没有通过兼容 decoder 或修改反馈断言掩盖。
独立完整运行只有 Starlette/tar 弃用警告。作者的单项 probe 曾有非致命 HTTPResponse finalizer ValueError,独立整套未复现;这不构成已修复该警告的证据。CI 按当前策略未查询。后续与其他调度或恢复改造组合时仍需保留同一个冻结 evaluator,本次不代替组合验收。
语义与 CI 对齐
本次在既有 /api/v1、TaskSpec、grading、session 和 result owner 上新增原生来源 vocabulary:两个具名 v1 上下文/条件 schema 及一个可选 submission receipt。Literal/Pydantic 约束与唯一 producer 相配,没有另建控制平面状态机或版本选择 decoder。API 的异步行为、默认 raw-score 正文和原 gateway 合同保持;已知登记的冻结与409已明确披露并以真实反例验证。没有调整预算、评分规则或硬性测试阈值。
我的整体评价 / Verdict
这个边界达到了服务端真实条件归因的目标,未发现 blocking finding。长程正确性改善在于重复提交、重试和配置 reload 不再静默更换原运行的评分条件;这不等于模型效果提高。普通使用自动得到反馈 URL,原生成客户端可继续工作,读取方可以通过一次初始化读取和单次结果读取获得可核对的来源,没有新增人工确认轮次。
视觉复核不适用:检查的路径是 HTTP 数据合同、命令入口及 API 文档,没有 App、布局或交互构图变化。未验证范围包括浏览器视觉、真实模型收益、不支持 backend 的已验证条件、外部可变服务,以及其他未合入提案的组合。
保留可选 receipt 和 unknown 兼容是合理的独立消费者边界;资源参数共用 helper、共用结果裁剪已经完成本域的必要合并,不需要引入新调度层。当前 PR 可以保留为一个完整的原生 API 改动。本结论是 exact-head 代码评审,不授予合并或部署权限。
English verdict: APPROVE — 671e8b7. No blocking findings; frozen evaluator execution, scoped feedback, baseline parity and real Docker/HTTP callers independently verified.
A native score currently identifies a submission but does not identify the loaded task or evaluator that produced it. A feedback reader attached to an older service can therefore attribute that score to the reader's newer installed checkout.
In a real Docker counterfactual with a synthetic task, changing the service's global judge environment after registration changed that prior token's score from 11 to 111 on upstream main. This implementation kept 11 and the original context.
This binds registration and accepted archive results to service-observed evaluation conditions on the current TOML/Blob harness. The service freezes the loaded TaskSpec, effective judge config/backend and actual Docker image ID; later task edits, image retags or registration retries cannot silently change the executable inputs of that registration.
sforge_run_evaluation_context_v1carries task/run/registration identity andsforge_evaluation_conditions_v1: hashes of the parsed TOML bytes, logical task/Blob inventory, installed framework source and effective execution config, plus the content-addressed judge image, native metric direction and existing selection rule.run_agentpasses its URL to the work container. A reader can fetch the frozen context once, then use one result GET withinclude_receipt=trueto verify the original context and exact accepted compressed-upload digest/size.409and requires a fresh run ID. Unknown/unsupported/legacy provenance remains unknown while native feedback stays available.Validation uses only disposable synthetic tasks and owned containers. The real path loads TOML, downloads and verifies a declared Blob, builds the native judge image, registers over TCP HTTP and runs the actual Docker grader. It covers concurrent distinct archives, image retag plus TOML/Blob/direction/resource changes, frozen old-token execution, missing-image/legacy attribution, foreign/auto relay reads, and the production
run_agentinstalling and executing the unchanged generated Linux submit client. No model API calls are made.Current head:
671e8b76233fea01c8289a16ec8d314cb5c7ff6b(3 DCO commits). Its final change declares the directpydantic>=2.0dependency used by the protocol models. Before that constraint, an otherwise valid FastAPI 0.99.1/Pydantic 1.10 installation produced a real/register500. The old pinned combination is now rejected by the resolver; an ordinary source upgrade of that environment selects compatible FastAPI/Pydantic 2, and real TCP registration/context reads both return 200 with the exact v1 fields.The
sforge/source and test tree are unchanged from75e787fef29386c9f7c01d63ed5c9617bc983a1b, independently qualified in a clean checkout: 46 repository tests plus 2 additional real-backend checks passed (48 passed, 1 optional-provider skip). The extra checks exercised a strict v1 consumer with one context GET and one result GET, relay authority boundaries, and real Docker default-response parity against upstreamba13e338. The author reran the uniquely named production probe (1 passed); a separate real Docker base/head environment-drift counterfactual also passed (1 targeted check). On the final head, the 23 protocol tests and independent dependency-resolver, ordinary-upgrade and real TCP API checks also passed. Independent exact-head review published an approval conclusion as aCOMMENTEDreview on this author-owned PR.The opt-in test requires Docker, a cached
python:3.12-slimbase and a client-to-host route (SFORGE_TEST_JUDGE_HOSTwhen the default does not apply); its disposable Linux client installs curl/jq. Tested with Python 3.12/3.13, Pydantic 2.14.3, Docker SDK 7.2.0 and Engine 29.5.2. The full run reported the existing Starlette/tar deprecation warnings. A separate probe run also observed a nonfatal HTTPResponse finalizer warning; the grading/terminal/receipt assertions passed, and no warning filter or exception normalization was added. CI has not been queried.The Docker ID identifies data inside that evaluator image; this is not a hermeticity or deterministic-scoring attestation for mutable external services/data. The protocol adds no checkpoint/restore, scoring strategy or scheduler. Upload-retention client work (#61) and supervisor/fairness work (#26) remain separate open proposals. This PR is based directly on current upstream
main(ba13e33842bd24e62fe0a361c5d41b2b21dd3010) and does not import the older integration history.