Skip to content

feat(multi-subagent): record Turn-bound native child reports - #5052

Merged
huangruiteng merged 6 commits into
mainfrom
codex/native-child-receipts-5051
Sep 27, 2026
Merged

huangruiteng merged 6 commits into
mainfrom
codex/native-child-receipts-5051

Conversation

@huangruiteng

Copy link
Copy Markdown
Collaborator

Problem and behavior

A Goal's max_children and model preference did not show whether a host-native child was attempted, skipped, rejected for capacity, or adopted by the parent. This PR adds a provider-neutral, Turn-bound native-child record/read contract. It stores typed decision, result, and parent-review reports with a stable operation ID; duplicate reports are idempotent, conflicts are rejected, and capacity/host failures stop same-Turn spawn retries while parent work can continue.

The shared read model keeps the configured ceiling separate from reported starts. Missing reports remain unknown. Coordinator reports are explicitly coordinator_reported and host_attested=false; they do not become authoritative host observations. Bound loopx delegation keeps its existing authority.

User entry points

  • CLI and managed agent-context can record or read the exact Turn's report. Goal status JSON and Markdown use the same projection.
  • The dashboard shows a compact, provenance-labeled card only for a Goal with enabled multi_subagent and a reported native decision. The existing configuration editor remains the configuration owner. Desktop/mobile and Chinese/English packaging were checked.
  • Lark currently shows team-plan proposals rather than host-native child activity; there is no existing native activity view to change in this slice.

Validation

  • 32 focused Python tests; 13 control-plane TS tests; Ruff; control-plane typecheck.
  • Dashboard desktop build, focused desktop/mobile browser smoke, packaged personal-workspace smoke, chat bundle build/install and source verification.
  • Public boundary scan: 0 errors; two unrelated existing registry warnings.
  • The broad smoke:home-browser run stopped at its Files projection assertion before reaching this card's assertion. The focused native-child browser path passed; the broad failure's cause is not established here.

Remaining boundary

This implements the generic reporting and readback substrate for #5051. LoopX cannot attest an arbitrary host-native tool call without a host integration. This PR does not add a Codex-specific adapter or claim host-observed receipts; keep #5051 open for adapter-backed attestation and end-to-end acceptance.

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes conclusion (author-owned PR; GitHub blocks formal self-review)

Reviewed exact head: be333f9
Immutable comparison base: 85759ba

本轮发现一个 P2 阻塞:历史增长会让仍然存在的子代理报告从共享状态和 UI 中消失。

动机

#5051 要解决的是“配置了子代理,不代表宿主真的启动过”的可观察性缺口。这个 PR 的有用增量是让主 Agent 的 decision、result、parent review 成为可重读的 Turn 报告,而不是从配置推断执行。它诚实标明 coordinator_reported、host_attested=false;宿主亲自出具证据仍是未交付部分,不能因此关闭 #5051。但这份增量自身需要在后续活动增长后仍能被共享入口读到,目前尚未满足。

改动思路

入口 native-child 先检查已注册 Agent 和 enabled multi_subagent,再由 capability 内的报告转换校验既有 Turn guard。事件复用 rollout journal 和独占文件锁;operation identity 负责幂等,记录不启动子代理、不消耗 quota、不授予 peer 或 delegation 的持久权限。CLI 精确读回、agent-context、status 与详情卡片共享报告数据,不另建任务库。

与原来的 native_host_capacity 即时观察相比,新增状态是无法从配置推导的主 Agent 报告事实,而统计、可见列表和重试说明是派生投影。bound delegation 仍有自己的 runtime/authority 生命周期,不能把这两类记录合并为“已执行”。这个所有权分离合理;问题出在共享 status 用诊断窗口发现事实,而不是在记录端。

具体改动

实际 base..head 为 17 个文件,+994/-8:生产路径 +566/-7,测试/示例 +340/-1,文档 +88。新增 CLI 和报告 reducer 是主要机制成本,五个前端文件只沿现有 Goal 详情路径接入,不增加配置入口。生命周期测试和双语文档与同一个用户结果相关,不是新的调度器。

关键代码讲解

  • handle_native_child_command:30:Goal 内注册身份和启用策略先于 record/read;read 不接受写入参数,record 默认 preview。未知 Agent、关闭能力和未绑定 Turn 都有明确拒绝,拒绝不增加事件。
  • record_native_child:214:decision 必须有 runnable guard;result 需要 started decision,accepted review 需要 completed result 和证据引用。两次并发提交同一 operation,实测仅追加一次,第二次读回同一记录;冲突不会覆写旧事实。
  • native_child_activity:74:先对完整 Turn 聚合,再限制为 8 条可见 operation。我记录 10 个 operation 后仍读到 1 次启动、9 次跳过、1 次验收,说明这个展示上限本身没有截断统计。
  • build_attention_queue:1193:这里先在最多 500 条近期事件中寻找 native event,找到才补读完整 Turn。最后一条 native event 也落在窗口外时,补读分支根本不会执行;下游卡片因此没有数据。

对主干的风险

[P2] 不要用近期 Todo 事件窗口判断最新子代理报告是否存在

复现是在独立合成 registry/runtime 上经过真实 CLI,不使用 mock queue:启用 max_children=6,依次 record 一个 spawn→completed→accepted 和九个 skip,再调用 status,能看到上述完整统计;只追加 501 条无关 evidence_log_read,第二次 status 的 project_asset.native_child_activity 就变成缺失。同一时刻 native-child read 对原 Turn 仍返回 coordinator_reported、launched_count=1、skipped_count=9、parent_accepted_count=1。两次 status 的合成契约健康出口均为 1,但都正常返回同一个 Goal 的 attention item;不是命令失败导致未生成投影。记录、Goal 启用状态与身份没有改变,改变的只是无关事件数量。

这违反“latest reported Turn”及持久读回合同,长时运行会把已有事实伪装成没有报告。当前测试只让 snapshot 留下最后一个 native result,所以覆盖了“decision 被截断”,没有覆盖“所有 native event 都被截断”。最小修复是保持 Todo 诊断窗口独立,在 canonical journal 的 native 事件范围内查找最新 Turn,或复用有完整性语义的现有投影;不要扩大所有 Todo 的 500 条预算,也不要新增手工同步的 latest 标志。加真实 status 的 501 条噪声回归,并测日志增长成本。

已运行:32 项 Python 测试、13 项 TS 测试、control-plane typecheck、changed-file Ruff、diff check、status Markdown smoke、dashboard/chat build、桌面与移动活动卡片 browser smoke、packaged personal-workspace smoke,以及上述 CLI 生命周期/边界/并发探针。容量拒绝后同 Turn 重试被挡,父级 skip 仍可记录,新 admitted Turn 可恢复启动;关闭能力的 before_plan 与 base 完全相同。

完整首页 browser smoke 在 base/head 均失败,规范化失败身份与详情同为“desktop Files projection lost its public-safe summary or Goal lineage: Files & Outputs / 0 / 团队报告”。失败发生在进入新增详情卡片之前,Files 投影代码不在此 diff,新卡片独立桌面/移动验证通过。因此它是已有无关失败,不构成本轮拒绝理由。远程 CI 未查询、未等待。

语义与 CI 对齐

本 PR 扩展的是可选报告词汇,不是宿主执行、计费或 actor 生命周期授权。缺失宿主证据继续是 unknown,配置上限不是实时容量;guidance 负责建议报告,record 的阶段/幂等约束则是实际执行的输入合同。必须修复的是共享状态对 canonical 报告存在性的误判,而不是解除安全门禁或降低测试预算。

我的整体评价

报告机制和事实来源区分值得保留;小而相关的下一步重构应落在最新 native Turn 的读模型边界,让存在性发现和精确聚合拥有一致的完整性依据,不要再给大 status 模块堆一个猜测标志。旧即时 capacity 观察仍被原生宿主调用,保留兼容有真实消费者依据;不能把它冒充持久宿主证明。

长期推进和用户体验的结论都是“尚有回归风险”:单次写入能用,但活动增长后的读回承诺失效。修复上述窗口问题、补独立 oracle 回归并重新验证 exact head 后可复审;宿主 attestation/adapter 仍属于 #5051 的明确后续范围。本次不合并、不以 CI 红灯或测试数量决定结论。

English verdict: REQUEST_CHANGES

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
@huangruiteng
huangruiteng force-pushed the codex/native-child-receipts-5051 branch from be333f9 to e4913a9 Compare September 27, 2026 04:50
…nstant

The bare `SCHEMA_VERSION` name joined the cross-module `SCHEMA_VERSION`
conflicting-values group and pushed the semantic inventory ratchet from 55 to
56, so `examples/semantic-vocabulary-drift-smoke.py` failed closed with
"inventory conflicting_definitions grew to 56; budget is 55 (unreviewed)".
Budgets only decrease and a raise needs maintainer approval, so this repairs
the cause instead of the number: the sibling module in the same package already
uses a qualified constant (`CODEX_HOST_CAPACITY_SCHEMA_VERSION`), and this
follows that convention so the new carrier no longer reads as another
ambiguous `SCHEMA_VERSION` definition.

No behaviour change: the emitted schema value stays `native_subagent_activity_v0`.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
@huangruiteng

Copy link
Copy Markdown
Collaborator Author

Refine, verification and merge handoff

Head: 5052@59d39cc5f61b904f4d562484dfe580d77b157332 — rebased onto current main (1cef0550a) with one refine commit on top; change-quality receipt cqr_57ae7243fd45d85ca473.

Refine (load-bearing). examples/semantic-vocabulary-drift-smoke.py failed at the rebased head with inventory conflicting_definitions grew to 56; budget is 55 (unreviewed). The cause was this PR's own new carrier: loopx/capabilities/multi_subagent/native_child_receipts.py declared a bare SCHEMA_VERSION, which joined the cross-module SCHEMA_VERSION conflicting-values group. The repository's rule is that these ratchets only decrease and a raise needs maintainer approval recorded in the RFC decision log, so the repair removes the cause rather than the number: the constant is now NATIVE_SUBAGENT_ACTIVITY_SCHEMA_VERSION, matching the sibling module's existing convention (CODEX_HOST_CAPACITY_SCHEMA_VERSION). The emitted schema value is unchanged (native_subagent_activity_v0), and the smoke now reports semantic-vocabulary-drift-smoke: ok. examples/canary/catalog-run-e2e-smoke.py, which failed alongside it in the same run, passes standalone as well.

What was repaired. The head was two days old and 216 commits behind, so most of its red CI no longer described this diff. After the rebase: the manager_context registry-census failures that failed two shards are fixed on main; the wheel-install-shape job in Release Artifacts now succeeds; the node-forward-compatibility run was an infrastructure cancel. The refine commit above cleared the semantic-vocabulary red that was this PR's.

Verification at this head. pytest tests/capabilities -k "native_child or multi_subagent or subagent" 18 passed plus the receipt file's 6; node --test tests/control_plane_ts/agent_context.test.ts 13 passed; semantically drift smoke ok; ruff clean; npm run typecheck:control-plane clean; git diff --check clean; the canary public-boundary scan over the 17 changed files clean; loopx canary premerge reports 19 selected checks with a single failure — cli-output-budget-regression-smoke, which fails identically on unmodified main (loopx_turn_plan/crowded/json emitted 14514 chars; baseline ceiling is 14500) and is therefore a pre-existing repository red, not this PR's.

Known boundary (declared by the author, unchanged). Every row is coordinator_reported with host_attested=false: this lands the reporting and readback substrate, not host-attested receipts. Adapter-backed attestation and end-to-end acceptance stay with #5051.

Coverage gap worth naming. The broad npm run smoke:home-browser path is not wired into any workflow, and the PR body reports it stopping at its Files-projection assertion before reaching the new card's assertion. The focused native-child browser assertions and the catalog risk smokes pass, so this is a local-only gap rather than a CI red — but the broad path should either be repaired or explicitly retired.

Merge authority. This changes loopx/**, apps/** (dashboard surfaces), the CLI contract and a persisted receipt schema, so per the repository's own rule it is not self-merged — and the local gate agrees (loopx canary premerge → self_merge_allowed: false, reason: repository_policy_required). The refine, the evidence above and the receipt are handed over for the maintainer's merge decision.

@huangruiteng
huangruiteng merged commit 03b7e66 into main Sep 27, 2026
7 of 8 checks passed
@huangruiteng
huangruiteng deleted the codex/native-child-receipts-5051 branch September 27, 2026 05:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant