feat: qualify transferable reward-memory experiences - #4824
huangruiteng wants to merge 2 commits into
Conversation
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
评审 head:785033da33e042990cc12b9ba6aefdc1eb5a1720
当前有 1 个 P1 阻塞:升级前已经落盘为 pending 的 turn_reward_memory_reflection_v0 sidecar,在本 PR 中会被 _reflection 转成 legacy_no_write 后提前返回,但 reconciliation 不会把原 sidecar 写成 terminal 状态。因此同一个文件会在每个后续 Turn 被重新扫描和“尝试”,永远保持 pending。我用真实临时 sidecar 文件连续执行两次 production reconciliation entrypoint,两次都得到 pending_count=1、attempted_count=1、completed_count=0,文件仍是 status=pending,provider 调用数为 0。已有 legacy 测试只覆盖“新收到 v0 不写”,没有覆盖“升级前已经 pending 的 v0 如何收敛”。
最小修复:在 replay 旧 v0 pending sidecar 时,原子地写入明确的 terminal rejected/quarantined migration receipt;receipt 必须如实保留“旧 provider 写可能已经提交”的不确定性,不能冒称已从 provider 删除。补一个两次 reconciliation 的回归:第一次 terminalize 且 provider 零调用,第二次应为 pending_count=0 / attempted_count=0。
动机
这项改动解决的是一个真实且重要的语义缺口:旧实现把“provider 写入并能按精确 id 读回”近似成“形成了可迁移的经验”。但事实摘要可能没有适用条件、因果归因、未来触发动作、验证方式和停止条件;精确 id 读回也不能证明在实际决策 surface 上能够被找到。PR 用 procedural_experience_contract_v0 将适用情境、已观察结果、归因、未来行为、限制和证据引用拆成可检查字段,并把“持久化”“结构/证据资格”“目标 surface 可发现性”“后续真实效用”分开,这是正确的产品方向。
旧行为是:compact summary 经 standing policy 接受并完成 exact readback 后即可 active。新行为是:procedural candidate 必须先通过结构化经验门禁;active envelope 保留完整经验;目标 surface 再以不含 candidate id 的 business_recall 查询并核对 candidate/experience digest;真正价值仍标成 unproven_until_application_evidence。这既避免了把存储成功当成经验质量,也没有把 advisory recall 误写成动作 authority。
但这次同时把 Turn reflection 从 v0 升为 v1,并声明 v0 只保留审计兼容。这个兼容边界不能只覆盖新输入,也必须覆盖升级时已经存在的 pending sidecar。当前缺口会让历史状态永远占据有界 reconciliation 批次,所以整条迁移还不能视为完成。
改动思路
入口和 authority 分层总体合理:
normalize_procedural_experience是纯、严格、provider-neutral 的结构/规范化 owner;它不靠关键词判断经验价值。candidate_review把 experience 纳入 deterministiccandidate_ref,并在 provider 访问前运行 quality guard。ingestion继续复用 standing policy、active envelope、provider sync 和 exact readback;对 procedural class 再做一次query_kind=business_recall的 destination check。application只把通过相同质量门禁的 active record 暴露给 recall,agent_turn_recall将结构化 experience 注入私有推理上下文,仍明确不授予新 authority。- CLI/App Turn outcome 仍要求独立 validator + durable writeback + quota spend,reflection 本身不能自证;未启用 capability、no-evidence 和显式 disable 路径保持零 provider 调用。
最强的“不应该这样发”的反例不是新 v1 happy path,而是 persisted compatibility:reconcile_pending_turn_outcome_ingests 的 authority 是 sidecar lifecycle,任何被扫描的 pending 都必须最终变为 completed/rejected/quarantined,或确实保留一个可重试 provider 条件。现在 parser 的 legacy_no_write 分类没有与这个 state owner 合并,造成“语义上不可再写、状态上却永远可重试”的非法组合。
具体改动
关键代码讲解
experience_quality.normalize_procedural_experience:严格要求 exact field set;applicability/limitations是有界非空列表,future_behavior必须完整包含trigger、action、validation、stop_condition,evidence refs 必须唯一且 opaque。canonical JSON digest 被 candidate identity、active envelope 和 recall 使用,避免不同阶段重新解释同一经验。candidate_review.build_reward_memory_candidate/_guard:experience 参与candidate_ref,procedural class 缺失合同会在 provider 调用前得到procedural_experience_quality_contract_missing。soft preference / hard policy 不会被强制升级成 procedural experience。ingestion.ingest_reward_memory_candidate:先做 exact resource/candidate/content digest readback;procedural record 再用 applicability + trigger + action 发起business_recall,并核对 candidate 与 experience digest。失败返回recall_unverified,而不是伪装成 activated。application._active_item与agent_turn_recall.apply_guidance:recall 端重复验证质量并把结构化 experience 放进私有 guidance;公开 packet 只暴露 quality receipt,不扩大 scope 或动作权限。outcome_lifecycle._reflection/reconcile_pending_turn_outcome_ingests:v1 解析和新 v0 no-write 判定本身清楚,但 v0 early return 发生在 sidecar 加载/更新之前;reconcile 仅凭原文件status=pending再次选中它,形成上述永久循环。
文档、content-ops seed import、decision-context producer、Codex host prompt、App candidate staging都同步到同一个 vocabulary,没有再造第二个 lifecycle/store。未来向的小步重构也应继续放在现有 outcome_lifecycle owner 内:增加一个明确的 legacy pending terminal transition 即可,不需要新的 migration framework。
对主干的风险
阻塞路径
触发条件:升级前一次 v0 Turn outcome 已完成独立 validation/writeback/spend,但 provider 返回 commit pending 或 readback unverified,因此 sidecar 持久化为 status=pending。
执行路径:下一 Turn 扫到 sidecar -> serialize v0 reflection -> _reflection 返回 legacy_no_write -> run_configured_turn_outcome_ingest 返回 no_eligible_evidence -> reconciliation 只把 receipt 放进本次返回值,不写原文件 -> 下一 Turn 再扫描同一文件。
错误结果:这是无 provider 调用、无可变化外部条件的永久 retry;多个旧 sidecar 可以占满 pending[:limit],让真正的 committed_pending / recall_unverified 新记录长期得不到 reconciliation。
最小修复与验证:写入 terminal migration receipt,并用真实临时文件运行两遍 production entrypoint,第二遍必须不再尝试;同时继续运行现有 outcome lifecycle、experience quality、App outcome 和 Turn driver suites。不要把旧 provider 对象“可能已提交”误报成删除或完成质量资格。
语义与 CI 对齐
这是一个具体的 persisted-contract migration 违例,不是 CI 是否绿色的问题。当前 exact head 的 187 个 focused tests 全部通过(528.35s),Ruff、git diff --check 和 current-main merge-tree 都通过;这些结果证明新 v1 路径和大多数负路径扎实,也恰好证明现有测试矩阵没有覆盖历史 pending state。Goal 的 capability 配置是 wait_for_ci=false,本次 verdict 没有等待或轮询 CI。
其余 residual risk 是 live OpenViking 的 semantic ranking:deterministic provider tests能证明控制流和 digest 约束,不能证明真实检索排序质量。这个风险适合 rollout observation,不应替代当前可稳定复现的 sidecar liveness blocker。
我的整体评价
架构方向、authority 边界、typed contract 和测试投入都很好,改动也复用了正确的现有 owner;1137 行虽然不小,但大部分是 focused tests 与双语协议说明,机制本身没有增加第二套 provider、scheduler、store 或权限系统。默认关闭/显式禁用路径仍隔离,前端和 Lark 不需要新状态 owner 的理由也成立。
当前不能批准的唯一原因是 schema 迁移没有覆盖已持久化的 pending v0 状态。它是运行时会反复发生的 replay/liveness 问题,而且会阻塞更晚的真正 retry 项;不是可以留作 post-merge follow-up 的文档瑕疵。修复并补两遍 reconciliation 回归后,请在新 head 上重新执行完整 exact-head review。
English verdict: REQUEST_CHANGES - head 785033d strands pre-upgrade pending v0 sidecars in perpetual reconciliation; terminalize/quarantine them truthfully and add a two-pass replay regression. Existing focused validation: 187 passed, Ruff and diff check passed, and the independent persisted-sidecar counterexample reproduced twice.
| return {"schema_version": raw["schema_version"], "status": status} | ||
| if status != "eligible": | ||
| raise ValueError("reward memory reflection status is unsupported") | ||
| if schema_version == LEGACY_TURN_REWARD_MEMORY_REFLECTION_SCHEMA_VERSION: |
There was a problem hiding this comment.
[P1] 这里把 v0 统一变成 legacy_no_write,但 replay 一个升级前已存在的 pending v0 sidecar 时,会在 sidecar lookup/write 之前提前返回。reconcile_pending_turn_outcome_ingests 随后只把本次 receipt 放进返回值,不更新原文件;下一个 Turn 又会扫描同一个 status=pending。我用真实临时 sidecar 连续跑两次 production reconciliation entrypoint,两次都是 pending_count=1/attempted_count=1、stored status 仍为 pending、provider calls=0。请在现有 outcome_lifecycle owner 内原子 terminalize/quarantine 这类 legacy pending state,明确保留“旧 provider commit 可能已发生”的不确定性,并补两遍 reconciliation 回归,第二遍应为 pending_count=0/attempted_count=0。
Reward Memory currently treats exact provider persistence as enough to activate a procedural record. A fact-only summary can therefore survive readback while omitting the actual transferable lesson, and an exact-id query says nothing about whether the lesson can be found at the decision surface.
This change introduces a typed
procedural_experience_contract_v0with applicability, observed outcome, attribution, future behavior, limitations, and evidence references. Procedural candidates are rejected before provider access when that contract is missing or malformed; the normalized contract participates in candidate identity, survives the active envelope, and is projected into Agent recall guidance. Legacy v0 Turn reflections remain audit-compatible but cannot write durable memory.Activation now separates three claims:
business_recall, queried without the candidate id, proves the record is discoverable at its configured surface.A destination miss remains
recall_unverifiedfor bounded reconciliation. Qualified experience is explicitly markedunproven_until_application_evidence; the gate does not claim that one outcome proves utility or alpha.User entry points:
turn_reward_memory_reflection_v1and receive typed quality/destination-recall receipts.Validation:
ruff checkon all changed Python and test pathsgit diff --checkNo local Goal state, provider scope, account data, credentials, or private finance material is included. This changes runtime and persisted receipt behavior, so it is intentionally left for maintainer review and is not self-merged.