fix(reward-memory): share surface checkpoints and typed input diagnostics - #5154
Conversation
…agnostics Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
…n diagnostics Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
…failure Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
精确评审 head:4caf96f2728d3f3762b6f6756068c9dec199a6a4;不可变对照:96a3b90f41094bd2ddea7263b9c7ee37371a9c3b。评审范围为全部 13 个文件,而非只看新增 helper。现有 PR 评论/评审为空;以下结论以源码、原契约和本地执行为依据,不代替维护者合并授权。
动机
这是让既有记忆消费调用方能够正确接入的有界增量。原 Turn 的 checkpoint 构建只适用于 Turn surface,其他消费入口不能直接借用;非法年龄和缺失读授权又被压成同一个宽错误,调用方难以纠正。改后复用实际 surface 的原配置,并提供安全、可行动的细分诊断。并未把上下文注入算作语义消费完成,也没有把测试通过算作真实效果。
改动思路
沿用原配置所有者选择 corpus,Python 只把必要范围引用与原始读权限证明传入既有 TS effect runtime,由 TS 组装 checkpoint;原 Turn wrapper 改为调用这个共享投影。范围、时效、冲突和 provider 准入仍由原 SDK 执行,本次没有在 TS 复制一套校验规则,也不另建存储或开关。
正向路径是原配置读回、实际读权限证明、共享 checkpoint、既有 SDK/provider、绑定当前产物的判断、原结果复用。负向路径覆盖 False 证明、错 surface、非法 age、缺 checkpoint 和 TS 启动失败,保留基线和真实调用次数。新增 corpus 不会因属于同一配置而自动加入既有 surface。outbound 的独立 actor/目的地/advisory 约束仍保留,未把这个 helper 当作发消息授权。
具体改动
全 diff 为生产代码 162 增/47 删,测试 226 增/1 删,双语文档 62 增/2 删;合计 450 增/50 删。无生成产物或纯机械搬移。Python 输入错误包装保留 ValueError 兼容及原校验顺序;hook 只把类型码传给 decision,TS 只在原 exact-corpus 拒绝分支输出四种白名单 detail。导出与 handler 注册让共享投影进入真实调用路径,而不是测试专用接口。
关键代码讲解
buildRewardMemorySurfaceReadCheckpoints(TS,第 32 行):只组装明确传入的 corpus/五类身份范围及原 source_ref;严格检查布尔值、紧凑引用和重复 corpus,不自行证明权限。False 原样保留。build_reward_memory_surface_read_authority_checkpoints(Python adapter,第 11 行):复用原配置 route,只发紧凑范围到 TS。Turn 的旧局部构建代码被删除,保留原 registry 来源;显式 SDK 消费者可指定自己的实际 surface。RewardMemoryRecallInputError(application,第 107 行)及 hook 捕获:把原输入拒绝转成四个类型码,不匹配异常正文;不放宽年龄、revision 或读权限门禁。projectRewardMemoryDecision(TS,detail 分支第 111 行):未知 detail 或把 detail 挂在成功分支均拒绝,正常 packet 不添加该字段,不泄漏异常内容。handle_agent_turn_recall_command(CLI,第 248 行):自审发现并修复构建失败直接逃逸的问题。只有 provider 调用前的 TS 失败才返回零调用、安全 packet 和退出码 2;managed Turn 沿用 fail-open,恢复后沿原 Turn 重试且无失败的成功回执。
双语文档明确年龄的合法输入、读权限证明责任、失败恢复以及必须保留完整私有 result;仅保存 public packet/context 不支持重启后 assessment。配置字段未变,现有 Dashboard config_path/enabled_agents 编辑与 roundtrip 被复用;无需新增控件或打包 frontend,本次也未声称新 UI/Lark 消费链路已完成。
对主干的风险
最大新增风险是一次 TS 投影调用及其故障面。最初只有普通正例可能掩盖 CLI 异常逃逸;新故障测试先失败后修正,目前证明 provider 未调用、未写成功回执、无私有错误泄漏,并可在 transport 恢复后取得有界结果。关闭/未配置路径仍零 TS/provider 调用,无新 packet、引导或 quota 义务。
同一公开 fixture 通过真实配置加载、SDK、应用回调及生产 TS transport,在 baseline/head 下比较 disabled、preview、交付后 ignored 判断及精确复用、错 surface、非法 age、缺 checkpoint。除预期新增 detail 外,输出/调用次数的语义摘要均为 c2e095c71024…,Turn checkpoint 摘要均为 f6da1b822d0a…。同一诊断 oracle 在旧主干失败,新 head 通过;没有用绿色测试代替历史对照。
暖 TS transport 的 20 个样本新增投影中位数约 9.85ms、最大 13.53ms,原局部构建约 0.001ms。这是已披露的 bounded cost,依据 TS-first/单一共享投影方向接受,不是 quota 或恢复总延迟优化证明;没有全程 cold/soak 或真实 provider 性能认证。
语义与 CI 对齐
这是既有诊断词汇的有界扩展及原 checkpoint 构建的共享化,符合 TS RFC 的替换优先/单一权威边界;没有新增调度义务、持久化版本分支或预算放宽。本 Goal 的评审配置 wait_for_ci=false,因此没有查询或等待远端 CI。
精确 head:252 项记忆/Turn/配置测试、56 项 outbound 回归、7 项 TS 专项、TS typecheck、Ruff、仓库声明的 mypy 20 个文件及新 adapter mypy 通过;精确 head canary 的 10 项 catalog、8 项 risk、1 项 public-boundary 共 19 项全部通过。广义 TS 套件在 TS 代码完全相同的上一提交通过 3166 项,30 项 PostgreSQL 环境用例跳过,非全库实机资格。扩展 legacy mypy 仍有 20 条错误:不可变基线和当前 head 用同一命令,规范化路径及错误正文摘要完全相同(902454fe86dd…);没有宣称该检查通过或让本 PR 承担无关修复。
我的整体评价
APPROVE,作为完整可用的 caller 修复切片,不代表父目标或整个记忆生命周期完成。长程推进接受已测得的有界 TS 成本,同时保留基线、同 Turn 复用、可恢复失败和原权限;用户体验改善为准确定位输入与安全 CLI 反馈。没有未解决的阻断发现。
可保留原 v0 packet/回执:真实 Turn、SDK 和既有私有结果读取仍依赖它,当前 Python/TS 同包部署,无并行 request decoder;后续迁移应在原消费者和持久化义务退役后处理。公共跨进程恢复、统一 frontend/Lark 展示、实际决策效用及其它控制面超时仍在本 PR 之外。该 runtime/API 改动等待维护者合并,不自合并,也不安装未合入源码。
English verdict: APPROVE - 4caf96f; shared exact-surface projection and typed diagnostics preserve authority and replay, with safe pre-provider CLI failure; scoped regressions and baseline comparison passed. Public restore/UI-Lark completion and live-provider utility remain out of scope.
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Delegating reward_memory_turn_read_authority_checkpoints to the shared read_authority adapter added an import line above _goal_repo, moving its load_registry call from line 40 to 41. Regenerate the checked-in census with the owning generator so test_project_registry_io_census stays current on the pull-request path. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
PR #5154 复审 — exact head a87508553dfba995437151c213d5f496b6666b99
动机
reward-memory 的 surface read checkpoint 此前有两处实现:Turn 包装层(agent_turn_recall/runtime.py)自己拼一份,被配置化的消费方(reward_memory.read_authority)另有一份等价规则,二者日后会各自演化。与此同时,SDK 的输入拒绝只抛出笼统的 ValueError,调用方无法区分"age/freshness 输入非法"与"checkpoint 缺失或非法";显式 CLI 在 provider 之前遇到 runtime 传输失败时也没有安全反馈,容易留下"看起来成功"的缺口。本 PR 把 checkpoint 组装与诊断收敛成一个共享投影,并在 provider 之前失败时给出真实的安全反馈。默认值、enablement、source authority、provider 路由、私有范围、调用上限与 application/utility 分离都不变,也不新增平行的准入规则。
改动思路
第一步是把"谁拥有 checkpoint 规则"收敛到一处:TypeScript 的 buildRewardMemorySurfaceReadCheckpoints 只组装调用方提供的原文证明(verified、source_ref、scope 与 corpus 身份),从不验证或授予权限;Python 新增 read_authority 适配器从既有配置所有者 resolve_reward_memory_surface_config 取出该 surface 的 corpora 后交给该投影,Turn 包装函数退化成一行委托,删掉了自己那份重复实现。第二步是让输入诊断变类型化:引入 RewardMemoryRecallInputError(ValueError) 与 allowlist 的 Literal 码,把 checkpoint 缺失/非法、age 非法、freshness context 非法区分开;因为它是 ValueError 子类,既有 except ValueError 的 fail-open 边界语义不变,但 hook 现在先捕获它并把码作为 boundary_detail_code 上报。第三步是给诊断加边界:TS 侧只接受 allowlist 值,且必须同时满足 guard_rejected 与 exact_corpus_request_invalid,否则直接拒绝,避免诊断退化成任意文本或成功凭据。第四步是 CLI:把读 checkpoint 的调用单独包起来,在 provider 之前遇到 runtime 失败时输出安全 packet 并返回退出码 2,与既有"ok 为真则 0,否则 2"的契约一致。
具体改动
loopx/capabilities/reward_memory/read_authority.py 是新增的适配器:从配置取该 surface 的 corpora,调用 effect runtime handler reward_memory.read_authority.surface_checkpoints,返回 checkpoints;注释与实现都明确它不调用 provider、不选策略源、不验证或扩大调用方权限。loopx/control_plane/capabilities/reward_memory_decision.ts 新增导出 buildRewardMemorySurfaceReadCheckpoints(严格 token/boolean 校验、corpus 去重、scope 字段可选透传),并新增 BOUNDARY_DETAILS allowlist 与 boundary_detail_code 的使用约束;effect_runtime_handlers.ts 注册该 handler。loopx/capabilities/agent_turn_recall/runtime.py 让 reward_memory_turn_read_authority_checkpoints 委托共享适配器(保留 verified=True 与 registry:<goal_id>:reward-memory),删除了原先的重复拼装。loopx/capabilities/reward_memory/application.py 新增 typed RewardMemoryRecallInputError,把 _authority_checkpoint 拆出 _normalize_authority_checkpoint 并把缺失/非法分别映射为 read_authority_checkpoint_missing/_invalid,把 freshness 的 age 与 context 非法映射为 freshness_age_invalid/freshness_context_invalid。runtime_hooks.py 先捕获 typed 错误并返回 guard_rejected + boundary_detail_code(保留 attempts 与 telemetry),其余异常仍走原有宽捕获。decision.py 让 telemetry 透出 boundary_detail_code。agent_turn_recall/cli.py 在 provider 之前捕获 RuntimeError,输出 status=runtime_unavailable、reason_code=automatic_recall_runtime_failed、provider_call_count=0、suppress_external_sinks=True 并以 2 退出。文档新增"必须保留完整私有 result"与"交付回执与语义回执相互独立"两段。合并 main 时三处冲突(reward_memory_decision.ts、其 TS 测试、reference 文档)都按"两侧都保留"解决:main 的 boundReceiptDigests/交付回执用例与本 PR 的 surface checkpoint 构建器/诊断用例共存。此外本 PR 新增的 import 把 runtime.py 中 _goal_repo 的 load_registry 从第 40 行推到第 41 行,已用所有者生成器刷新 project_registry_io_manifest_v1.json(这是本 PR 自引入的 census 漂移)。
对主干的风险
主风险是诊断变成泄漏或伪成功:boundary_detail_code 受 allowlist 限制且必须绑定 guard_rejected + exact_corpus_request_invalid,测试断言私有异常文本不会出现在结果里("private runtime" not in json.dumps(result)),拒绝路径的 decision_consumption_complete 保持 false、provider_call_count 保持 0。第二个风险是 ValueError 兼容性:新异常是其子类,既有 fail-open 捕获仍然生效,hook 只是把处理提前以获得 typed detail,没有改变"拒绝即不调用 provider"的语义。第三个风险是 CLI 退出码与回执语义:新分支只在 provider 之前触发,0/2 与既有契约一致,managed 路径保持 fail-open 与同 Turn 恢复(有测试覆盖失败后恢复并写出同 Turn 回执)。合并侧的风险是丢失 main 的 #5158 交付回执逻辑,已核对合并结果同时保留两侧实现与用例。需要记录的环境观察有两条:loopx canary premerge --from-git-diff 的唯一失败项是 examples/canary/catalog-run-e2e-smoke.py,它内部选中的 examples/control_plane/bounded-context-namespace-smoke.py 在本机需要约 56 至 59 秒,而 canary 单项上限是 60 秒,单独运行该 smoke 通过(59.4 秒),且它校验的 loopx.capabilities 历史 shim 不在本 PR 的 14 文件内;主仓库本地还残留一个未跟踪的 loopx/capabilities/cross_runtime/ 目录,会让同一 smoke 在本机快速失败,这是本机工作树残留而不是 main 的缺陷,PR 的干净工作树与 premerge 的 public boundary 扫描(14 文件)都通过。
我的整体评价
这是一次收敛重复、加类型化诊断并修正 CLI 边界的小切片,方向正确:它删掉 Turn 包装层的重复 checkpoint 规则而不是再引入一层抽象,把诊断限制在 allowlist 并要求绑定拒绝上下文,在 provider 之前失败时返回真实的安全回执而不是伪成功,同时保留了 ValueError 兼容与 managed fail-open。我在该 head 上复验了 TS 定向用例 10/10、npm run typecheck:control-plane、全量 control-plane TS 3264 项 0 失败、Python 相关 13 个文件 256 项、架构 125 项、定向 Ruff、git diff --check 与 public boundary 14 文件;premerge 的唯一红项已按上面的环境原因归因。建议合入。
English verdict: APPROVE - a875085 consolidates the duplicated reward-memory surface checkpoint rule into one TS-owned projection with a Python adapter, adds an allowlisted typed input-diagnostic path that preserves ValueError compatibility, and fails the explicit CLI safely before any provider call; TS 10/10 plus 3264-suite, 256 Python, 125 architecture checks and the 14-file public boundary scan pass, with the single premerge red attributed to a local 60s canary timeout.
|
Merged as
|
Summary / 摘要
Repair a reusable caller boundary in the existing Reward Memory capability:
build_reward_memory_surface_read_authority_checkpointsfor the actual configured consumer surface. The original config owner selects its exact corpora; TS assembles scope and original caller-supplied proof. The Turn wrapper delegates to the same projection instead of maintaining its own checkpoint builder.exact_corpus_request_invalidand zero provider calls on these failures.runtime_unavailablefeedback and exit code 2; retain managed fail-open and same-Turn recovery without writing a false successful receipt.在原能力内补足通用 surface checkpoint 和输入诊断。TS 持有共享组装/诊断投影;Python 保留原配置/provider 适配及原 SDK 校验,不新增平行的准入规则。False 不变成 True,checkpoint 生成不等于读权限已核验,非法参数不调用 provider。
Validation / 验证
uv run --extra test python -m pytest -q tests/capabilities/test_reward_memory*.py tests/capabilities/test_agent_turn_recall.py tests/test_reward_memory_pipeline.py tests/capabilities/test_capability_configuration_ui.py: 252 passed, plus 56 passed in existing outbound guidance regressions.node --experimental-strip-types --test tests/control_plane_ts/reward_memory_decision.test.ts: 7 passed; strict unknown-code rejection and exact-scope assembly.npm run typecheck:control-plane, focused Ruff, repository-declared mypy (20 source files) and new adapter mypy: passed.96a3b90f41094bd2ddea7263b9c7ee37371a9c3band candidate; this is not a passing full legacy type check. No unrelated fixes included.invalid_agelacks detail); the six initial new regression cases also failed before implementation. Candidate passes. No live model/provider qualification is claimed.4caf96f2728d3f3762b6f6756068c9dec199a6a4: 10 catalog + 8 risk + 1 boundary checks passed. Broad TS suite at the TS-identical predecessor: 3166 passed, 30 PostgreSQL-environment tests skipped; TS sources/fixtures/manifest diff is empty at final head. This does not qualify the skipped real-service cases.Product boundary / 产品边界
CLI/managed callers receive the reusable SDK helper and safe detail; source-level SDK/Turn/config-editor contracts were exercised. No configuration fields change: Dashboard still edits
config_path/enabled_agentsthrough its existing owner and roundtrip, so no companion control or rebuilt frontend is needed for this slice. Lark remains status-only; universal frontend/Lark semantic-consumption delivery is not complete.This is an independently reviewable slice, not completion of the broader memory lifecycle. A caller must retain the complete private result; saving only context/public packet/application receipt does not support assessment after EOF/restart. There is no supported cross-process restore API yet. No re-query or fabricated semantic completion is allowed. The bilingual reference makes this limitation explicit.
配置与前端控件未改;没有声称打包 UI/Lark 已新增消费链路。跨进程私有 result 的安全恢复、统一展示和真实效果仍是已有任务的后续缺口。未改变 Goal provider、验收、交易权限或默认自动化;测试/PR 数量不算研究效果。
Signed commits separate initial runtime/API and tests/docs; a third signed self-review refinement fixes the narrow CLI failure boundary with regression tests. All outgoing author/committer noreply identities verified. Ignored local comparison harnesses and raw validation logs are excluded. Maintainer merge required for this runtime/API change; no self-merge or unmerged-source installation.
Maintainer repair at the merged head
The reviewed head
a87508553dfba995437151c213d5f496b6666b99was reached by mergingorigin/main(af3e7f1f0) into the branch and refreshing the checked-in registry I/O census.46621c4be(fix(reward-memory): preserve verified context delivery across assessment #5158, verified delivery across assessment):loopx/control_plane/capabilities/reward_memory_decision.ts(main'sboundReceiptDigestsvs this PR'sbuildRewardMemorySurfaceReadCheckpoints),tests/control_plane_ts/reward_memory_decision.test.ts(main's three delivery-receipt cases vs this PR's two checkpoint/detail cases), anddocs/reference/reward-memory-decision-consumption.md(both new paragraphs, English and Chinese). All three kept both sides; the TS merge also needed the PR test's closing});restored.loopx/semantics/project_registry_io_manifest_v1.json: the newread_authorityimport moved_goal_repo'sload_registryfrom line 40 to 41, sotest_project_registry_io_censuswas red on this branch. Regenerated with the owning generator (one line).npm run typecheck:control-plane;npm run test:control-plane(3264 tests, 3234 passed, 30 environment-gated skips, 0 failures); the reward-memory/agent-turn-recall Python set (256 passed); the three architecture suites (125 passed); targeted Ruff;git diff --check; andloopx checkover all 14 changed paths ("public boundary scan clean: 14 files").loopx canary premerge --from-git-diff: catalog canaries 10/10, direct checks and public boundary passed; the only red isexamples/canary/catalog-run-e2e-smoke.py, whose selectedexamples/control_plane/bounded-context-namespace-smoke.pyneeds ~56-59s on this machine against the canary's 60s per-check cap. That smoke passes standalone (59.4s) and guards the legacyloopx.capabilitiesshims, which are not in this PR's 14 files. Recorded as an environment-timeout hold, not a code failure.Merged with admin bypass because the
protect mainruleset requiresrequire_last_push_approvalanddismiss_stale_reviews_on_push; the maintainer main-integration push made the maintainer the last pusher. The exact-head review is published at #5154 (review).