fix(subagents): enforce UTF-8 snapshot budgets - #238

Open
smf-h wants to merge 4 commits into
openpi-dev:mainfrom
smf-h:fix/subagents-snapshot-180
Open

fix(subagents): enforce UTF-8 snapshot budgets#238
smf-h wants to merge 4 commits into
openpi-dev:mainfrom
smf-h:fix/subagents-snapshot-180

Conversation

@smf-h

Copy link
Copy Markdown
Contributor

概要

  • 为 subagent snapshot 增加严格的 UTF-8 字节级预算。
  • 对多个 agent 进行公平裁剪,并保留生命周期身份、终态状态、错误信息和恢复所需引用。
  • 在展示投影前持久化精确 terminal result,读取时优先使用 artifact 内容。
  • 保留 raw active tool map,确保 ToolUpdate / ToolEnd 生命周期事件仍可关联。
  • 对 malformed、oversized 和非法 snapshot 输入 fail safely。
  • 增加 UTF-8 边界、公平裁剪、幂等投影、artifact precedence 和 tool lifecycle 回归测试。

验证

  • bun test tests/extensions/subagents/snapshot.test.ts tests/extensions/subagents/manager.test.ts tests/extensions/subagents/index.test.ts
  • bun run lint
  • bun run typecheck

Fixes#180

@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from 535806d to 3eb2568CompareAugust 28, 2026 18:07

@somewan820somewan820 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P1 Must-Fix:extensions/subagents/src/manager.ts:427-435 的 persistExactResult() 在 settle() 中执行,未处理 writer 错误;生产 persistResultArtifact() 可因权限、symlink、磁盘满或 collision 抛错,导致 settle() 在 notify()/onSettled() 前失败,waiter/result delivery 可能永远收不到终态。请让 artifact cache fail-soft,或保证 settlement 先终态通知再处理缓存失败,并补充 failure-path tests。

P1 Must-Fix:exact-result artifact 没有 retention/aggregate size limit,长期运行会持续积累唯一结果并耗尽 cache volume。请加入 bounded cleanup/retention 及写入/清理失败恢复。

P2 Should-Fix:enforceSnapshotBudget() 将有损 projection 写回 live entry.snapshot,transcript omits 后 takeover UI 无 rehydrate 路径,历史会静默消失。执行 snapshot 与 UI/model projection 应分离,或提供显式 rehydrate/omission 状态。P3:snapshot parsers 当前无生产调用方。静态审查,未运行测试。

@github-actionsgithub-actionsBot added the area:subagents Subagent delegation, skills, or tests label Aug 30, 2026
@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from 3a0cc46 to 51b6a22CompareAugust 30, 2026 08:34
@smf-h
smf-h requested a review from somewan820August 30, 2026 08:37
@smf-h

smf-h commented Aug 31, 2026

Copy link
Copy Markdown
ContributorAuthor

感谢审查,已在最新提交 中处理这些问题。

P1:artifact persistence 不再阻断 settlement

 settle() 现在先完成 terminal status、retained result、projection 更新、 notify() 、 onSettled() 、waiter 唤醒及并发槽释放,再将 artifact persistence 调度为异步的可选恢复工作。因此权限错误、symlink、磁盘满、collision、lock contention 或 writer throw 都不会阻止终态通知,也不会让 waiter/result delivery 永久等待。

延迟 writer 会捕获 cache/filesystem 异常,严格验证 ref 只能是 { version: 1, digest } ,验证 digest 与 exact result 内容匹配,并使用 run generation guard 防止旧 run 的延迟 writer 把 artifact 附加到重启后的新 run。失败时不会回滚 terminal status 或 finalText 。

新增/更新的 manager failure‑path tests 验证了 writer throw、wrong digest 和非法 ref 场景下: waitFor() 返回、终态可从 per‑id listener 观察、 onSettled() 被调用,且后续仍可 spawn。

P1:artifact cache bounded retention

cache 现在有硬性 retention 上限:最多 64 个 artifact 文件、最多 64 MiB payload;自定义 maxFiles / maxBytes 不能超过 hard cap。retention eviction 和 stale metadata cleanup 在 cache lock 内完成。超大写入在触碰 cache 前拒绝,不留下 partial artifact 或 .tmp ,后续正常写入仍可继续。

锁获取使用有界重试;ownership 不确定、PID reuse、lock contention、symlink、unknown/recovery entry 或平台无法提供安全 no‑follow 操作时均 fail‑closed。release cleanup 失败也不会把已成功发布的 ref 变成失败。

P2:canonical snapshot 与 projection 分离

 entry.snapshot 保持 canonical event‑folding state, entry.projection 是独立的 aggregate‑bounded read model;projection 不再写回 canonical snapshot。takeover 继续使用 view.getFull?.(id) ?? view.get(id) 。

projection rebuild 失败时会清除旧 projection,读取方回退到最新 canonical state,避免 settled 条目继续显示旧的 running 。exact‑result recovery 只使用经过校验的 artifact 或明确完整的 canonical retained finalText ,不会把 projection 当作 recovery source。新增 finalTextTruncated 标记:当 artifact 不可用且 retained text 被 1 MiB cap 截断时, subagent_result 会明确报告 exact result unavailable,而不是返回截断前缀。

P3:snapshot projection 已有生产调用方

manager 会在 entry 注册和 lifecycle 更新时调用 snapshot projection;canonical snapshot 仍供 event folding 和 takeover rehydration 使用,model‑facing 工具使用 bounded projection。

验证

已执行:

text

node --test --experimental-strip-types tests/extensions/subagents/manager.test.ts tests/extensions/subagents/result-artifact.test.ts tests/extensions/subagents/snapshot.test.ts tests/extensions/subagents/takeover.test.ts tests/extensions/subagents/index.test.ts

结果:88 tests,74 passed,0 failed,14 skipped。

另已执行并通过:

text

bun run typecheck
bun run lint

14 个 skipped 测试依赖 POSIX descriptor‑relative/no‑follow 文件系统能力;当前环境为 Windows,因此按预期跳过。Windows fail‑closed 测试已通过。POSIX lock recovery、并发 retention 和 symlink cache 行为未能在当前 Windows 环境实际执行,未修改 skip 条件或削弱安全行为。

@tt-a1itt-a1i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

复审 exact head b3d7d4665f160e0e3eb8fcaba73c27f536025885。目标是用UTF-8字节预算限制子代理展示快照,同时通过canonical state/受保护artifact保留恢复能力。旧review中的settlement被缓存异常阻塞、缓存无上限、展示投影覆盖canonical state三项都有实质改进,不重复计入。

Standards

独立人工审查无额外架构要求;现有自动守卫确认1项:新增subagent_result没有完成child分类,也未纳入现有tool surface,违反已有工具归属契约。请在现有表中补齐,不要新增一层权限系统。

Spec

3项已复现P2:

  1. Darwin使用/dev/fd/目录句柄拼子路径,macOS上的cache读写失败。
  2. 超过16KiB的单行结果无法通过line offset/limit恢复;limit=1仍只有截断提示。
  3. truncateUtf8Head把四字节字符的3字节前缀解码为替换字符,却误判为完整UTF-8前缀。

详见行内。它们直接影响本PR承诺的可恢复性、兼容性和UTF-8正确性;无需扩展成新存储框架。

验证

Node24/macOS exact checkout:check因index.ts/manager.ts格式失败;完整test失败,明确包含child drift guard和Mac artifact错误。独立重跑guard仍报subagent_result未分类;Mac artifact专项仍报ENOENT /dev/fd/.../cache。独立pageResultText复现20KiB单行只返回60-byte提示;truncateUtf8Head("😀",3)返回替换字符,4096字节真实预算同样复现。GitHub Node22/24 CI也为失败。不把其他并行运行出现的setup超时直接归因本PR;未做实际Pi UI或发布验收。

请修复这些边界并跑通check/test后再复审。本轮未修改源码、提交或合并。

});

pi.registerTool({
name: "subagent_result",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 将新工具纳入现有归属和 child 权限分类

新增 subagent_result 后,extensions/shared/child-session.ts 的 child-safe/excluded 列表都没有它,OPENPI_TOOL_SURFACE.subagents 也未登记。当前现有 every registered package tool is classified ... 测试稳定失败,明确报该工具未分类。请按真实用途加入已有 parent-only/child-safe 分类及 subagents 工具可见性表,验证关闭能力和子会话边界,不应让新工具绕开已建立的登记契约。

function descriptorRelativePath(fd: number, fallback: string) {
if (process.platform === "linux") return `/proc/self/fd/${fd}`;
if (
process.platform === "darwin" ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Darwin 的 /dev/fd 不能当作 Linux 的目录遍历入口

本机 macOS 实测目录fd本身可fstat,/dev/fd/<fd>也显示directory,但访问/dev/fd/<fd>/package.json即ENOENT;它不像Linux /proc/self/fd/<fd>支持继续拼接子路径。因此这里声明支持darwin后,openCheckedDirectory中的mkdir/read始终失败,artifact专项稳定报ENOENT mkdir /dev/fd/14/cache,不是测试并行噪声。请验证Mac可用、具有同等安全边界的实现;如果决定暂不支持,需在I/O前明确fail-closed并披露精确恢复的退化,不能仅跳过测试或声称兼容性未变。未推断其他BSD行为。

truncated: false,
};
}
const suffix = `\n[page truncated at ${maxBytes} bytes; reduce the requested range]`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 为超长单行保留可继续读取的恢复路径

当前工具只有行offset/limit,超出字节预算后使用的truncateHead不会截取首条超长行的部分内容。真实输入20KiB单行+尾标记,offset0/limit200与offset0/limit1都只返回60字节的截断提示;offset1已经EOF。用户按提示reduce range也无法取得正文,违反exact recovery用途。请提供最小行内继续读取方式或等价受保护恢复路径,同时保持页面字节上限,并补单行超限的完整恢复测试。

Comment threadextensions/subagents/src/snapshot.ts Outdated
let end = maxBytes;
while (end > 0) {
const candidate = bytes.subarray(0, end).toString("utf8");
if (byteLength(candidate) === end) return candidate;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 解码后字节数相同不代表 UTF-8 边界完整

truncateUtf8Head("😀", 3)实际返回U+FFFD替换字符:四字节emoji的前三字节解码成一个三字节replacement,恰好满足byteLength(candidate) === end,因此提前返回了并非原文前缀的字符串。真实4096预算下,4093个ASCII字符后接emoji也会出现同样损坏。现测试只有三字节中文,漏掉了此情形。请按codepoint边界回退或采用严格解码验证,并覆盖四字节字符各切割位置;不要让有界裁剪改写原文内容。

@tt-a1i

Copy link
Copy Markdown
Collaborator

当前 head b3d7d46 已复审,Changes Requested。旧的缓存失败阻塞settlement、无缓存上限、canonical/projection混用已有实质修复;新确认4项P2:工具分类遗漏、Mac缓存路径不可用、超长单行无法恢复、四字节UTF-8截断产生替换字符。check/test及必需CI当前均非绿色,详见 #238 (review) 。建议在现有边界内逐项修,不增加新框架。本轮未改源码或合并。

Keep canonical settlement separate from bounded projection, persist artifacts fail-soft, and recover through subagent_result(id, offset, limit) instead of cache pathnames.
Keep exact-result paging, UTF-8 truncation, Darwin cache paths, and
child-tool classification within the existing subagent surface.
@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from ba54ec5 to 781775eCompareSeptember 1, 2026 04:34

@tt-a1itt-a1i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

复审 exact head 781775e9acf419d34bff904c1e1353229251202e。目标是给 Direct Subagent snapshot 建立聚合 UTF-8 硬上限,同时保留 exact result 的恢复能力;价值是避免长 Session 的模型/UI read model 持续膨胀。当前提交已经修好工具分类、四字节 UTF-8 截断和超长单行分页,但仍有跨平台恢复、fail-open 和生命周期问题。

Standards

2 项:projection rebuild 失败会回退到完整 canonical snapshot,硬资源上限 fail-open;artifact persistence 用未受 Session Scope 管理的裸 setTimeout,shutdown 不取消/等待,且测试把 onSettled 内断言异常吞掉了。

Spec

2 项:exact artifact 现在仅支持 Linux,macOS 上超过 1 MiB 的终态结果不可恢复;Issue #180 要求的旧版/超大 persisted snapshot restore 边界仍没有实现,当前 projection 只存在于进程内 manager。另有 line-offset 转 byte cursor 时文本提示的 cursor 不是绝对值。

验证:macOS exact checkout bun run check 通过;manager/snapshot/result-artifact 专项 47 passed、14 个 Linux-only skipped;独立分页复现得到文本 next 38,结构化 absolute nextByteOffset=45。GitHub Node 22/24 仍各失败 2 项,因此必需 gate 未通过。建议先缩小并闭合现有边界,不要继续扩充存储框架。

const RESULT_CACHE_RECOVERY_NAME =
/^\.retention-lock\.recovery\.[0-9a-f-]{36}$/iu;
const RESULT_ARTIFACT_TEMP_NAME = /^\.[a-f0-9]{64}\.[0-9a-f-]{36}\.tmp$/u;
const SUPPORTED_DESCRIPTOR_PLATFORMS = new Set(["linux"]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 这不是修复 Darwin cache,而是把 exact-result artifact 限定为 Linux。macOS 上 persist 会抛出 unsupported,read 会变成 miss;manager 又只保留 1 MiB finalText,所以更大的 settled result 会被 subagent_result 明确判为 exact unavailable。Issue #180 要求 exact result artifacts 保持可恢复,PR body 也未披露 Linux-only。最小处理可以明确收窄 PR/Issue 范围并保留 #180 未完成;若仍声称 Fixes #180,则需要经验证的 Pi-native/平台安全恢复机制。

return true;
} catch {
// Never expose a projection from an older lifecycle state after a
// failed rebuild; readers fall back to the canonical snapshot.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 投影重建失败时清空所有 bounded projection,而 view.list/get 随后用 entry.projection ?? entry.snapshot 回退到完整 canonical snapshot。资源上限自身一旦失败,模型/UI 就收到未受 aggregate budget 限制的数据,属于 fail-open。请保留最后一个安全 projection 或返回明确的有界失败状态,不能把 canonical full snapshot 当普通 reader fallback。

// Artifact persistence is optional recovery work. Schedule it only
// after settlement has notified waiters and hooks, so a slow or broken
// writer can never hold the lifecycle path or a concurrency slot.
setTimeout(() => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 这个裸 setTimeout 不属于 manager Scope;disposeAll() 不取消也不等待它,所以 Session 关闭后仍可能写 cache、修改 entry 并触发 notify。它也保证 onSettled/delivery clone 发生在 artifact 关联之前。当前“persisted before settlement”测试在 onSettled 里抛出的断言会被生产代码 catch 掉,因此是假阳性。请跟踪并在 teardown 取消/有界等待该任务,同时用不会被生产 catch 吞掉的外部观测验证顺序。

);
}

const noticeFor = (next: number) => `\n[page truncated; next ${next}]`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] line-offset 页面截断时,这个 notice 使用 page-local end,但返回的 nextByteOffset 是加上 pageStartByte 的绝对 cursor。复现 prefix\n + 长行、offset:1,maxBytes:64:正文提示 next 38,details 返回 nextByteOffset:45。Pi 提供给模型的可靠正文不能指向另一个位置;请让提示使用 absolute cursor,或删掉正文中的数值并只暴露一个可消费的权威 cursor。

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:subagentsSubagent delegation, skills, or tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

subagents: 聚合 snapshot 缺少统一 byte 上限

3 participants

@smf-h@tt-a1i@somewan820
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(subagents): enforce UTF-8 snapshot budgets - #238

Open
smf-h wants to merge 4 commits into
openpi-dev:mainfrom
smf-h:fix/subagents-snapshot-180
Open

fix(subagents): enforce UTF-8 snapshot budgets#238
smf-h wants to merge 4 commits into
openpi-dev:mainfrom
smf-h:fix/subagents-snapshot-180

Conversation

@smf-h

Copy link
Copy Markdown
Contributor

概要

  • 为 subagent snapshot 增加严格的 UTF-8 字节级预算。
  • 对多个 agent 进行公平裁剪,并保留生命周期身份、终态状态、错误信息和恢复所需引用。
  • 在展示投影前持久化精确 terminal result,读取时优先使用 artifact 内容。
  • 保留 raw active tool map,确保 ToolUpdate / ToolEnd 生命周期事件仍可关联。
  • 对 malformed、oversized 和非法 snapshot 输入 fail safely。
  • 增加 UTF-8 边界、公平裁剪、幂等投影、artifact precedence 和 tool lifecycle 回归测试。

验证

  • bun test tests/extensions/subagents/snapshot.test.ts tests/extensions/subagents/manager.test.ts tests/extensions/subagents/index.test.ts
  • bun run lint
  • bun run typecheck

Fixes#180

@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from 535806d to 3eb2568CompareAugust 28, 2026 18:07

@somewan820somewan820 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P1 Must-Fix:extensions/subagents/src/manager.ts:427-435 的 persistExactResult() 在 settle() 中执行,未处理 writer 错误;生产 persistResultArtifact() 可因权限、symlink、磁盘满或 collision 抛错,导致 settle() 在 notify()/onSettled() 前失败,waiter/result delivery 可能永远收不到终态。请让 artifact cache fail-soft,或保证 settlement 先终态通知再处理缓存失败,并补充 failure-path tests。

P1 Must-Fix:exact-result artifact 没有 retention/aggregate size limit,长期运行会持续积累唯一结果并耗尽 cache volume。请加入 bounded cleanup/retention 及写入/清理失败恢复。

P2 Should-Fix:enforceSnapshotBudget() 将有损 projection 写回 live entry.snapshot,transcript omits 后 takeover UI 无 rehydrate 路径,历史会静默消失。执行 snapshot 与 UI/model projection 应分离,或提供显式 rehydrate/omission 状态。P3:snapshot parsers 当前无生产调用方。静态审查,未运行测试。

@github-actionsgithub-actionsBot added the area:subagents Subagent delegation, skills, or tests label Aug 30, 2026
@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from 3a0cc46 to 51b6a22CompareAugust 30, 2026 08:34
@smf-h
smf-h requested a review from somewan820August 30, 2026 08:37
@smf-h

smf-h commented Aug 31, 2026

Copy link
Copy Markdown
ContributorAuthor

感谢审查,已在最新提交 中处理这些问题。

P1:artifact persistence 不再阻断 settlement

 settle() 现在先完成 terminal status、retained result、projection 更新、 notify() 、 onSettled() 、waiter 唤醒及并发槽释放,再将 artifact persistence 调度为异步的可选恢复工作。因此权限错误、symlink、磁盘满、collision、lock contention 或 writer throw 都不会阻止终态通知,也不会让 waiter/result delivery 永久等待。

延迟 writer 会捕获 cache/filesystem 异常,严格验证 ref 只能是 { version: 1, digest } ,验证 digest 与 exact result 内容匹配,并使用 run generation guard 防止旧 run 的延迟 writer 把 artifact 附加到重启后的新 run。失败时不会回滚 terminal status 或 finalText 。

新增/更新的 manager failure‑path tests 验证了 writer throw、wrong digest 和非法 ref 场景下: waitFor() 返回、终态可从 per‑id listener 观察、 onSettled() 被调用,且后续仍可 spawn。

P1:artifact cache bounded retention

cache 现在有硬性 retention 上限:最多 64 个 artifact 文件、最多 64 MiB payload;自定义 maxFiles / maxBytes 不能超过 hard cap。retention eviction 和 stale metadata cleanup 在 cache lock 内完成。超大写入在触碰 cache 前拒绝,不留下 partial artifact 或 .tmp ,后续正常写入仍可继续。

锁获取使用有界重试;ownership 不确定、PID reuse、lock contention、symlink、unknown/recovery entry 或平台无法提供安全 no‑follow 操作时均 fail‑closed。release cleanup 失败也不会把已成功发布的 ref 变成失败。

P2:canonical snapshot 与 projection 分离

 entry.snapshot 保持 canonical event‑folding state, entry.projection 是独立的 aggregate‑bounded read model;projection 不再写回 canonical snapshot。takeover 继续使用 view.getFull?.(id) ?? view.get(id) 。

projection rebuild 失败时会清除旧 projection,读取方回退到最新 canonical state,避免 settled 条目继续显示旧的 running 。exact‑result recovery 只使用经过校验的 artifact 或明确完整的 canonical retained finalText ,不会把 projection 当作 recovery source。新增 finalTextTruncated 标记:当 artifact 不可用且 retained text 被 1 MiB cap 截断时, subagent_result 会明确报告 exact result unavailable,而不是返回截断前缀。

P3:snapshot projection 已有生产调用方

manager 会在 entry 注册和 lifecycle 更新时调用 snapshot projection;canonical snapshot 仍供 event folding 和 takeover rehydration 使用,model‑facing 工具使用 bounded projection。

验证

已执行:

text

node --test --experimental-strip-types tests/extensions/subagents/manager.test.ts tests/extensions/subagents/result-artifact.test.ts tests/extensions/subagents/snapshot.test.ts tests/extensions/subagents/takeover.test.ts tests/extensions/subagents/index.test.ts

结果:88 tests,74 passed,0 failed,14 skipped。

另已执行并通过:

text

bun run typecheck
bun run lint

14 个 skipped 测试依赖 POSIX descriptor‑relative/no‑follow 文件系统能力;当前环境为 Windows,因此按预期跳过。Windows fail‑closed 测试已通过。POSIX lock recovery、并发 retention 和 symlink cache 行为未能在当前 Windows 环境实际执行,未修改 skip 条件或削弱安全行为。

@tt-a1itt-a1i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

复审 exact head b3d7d4665f160e0e3eb8fcaba73c27f536025885。目标是用UTF-8字节预算限制子代理展示快照,同时通过canonical state/受保护artifact保留恢复能力。旧review中的settlement被缓存异常阻塞、缓存无上限、展示投影覆盖canonical state三项都有实质改进,不重复计入。

Standards

独立人工审查无额外架构要求;现有自动守卫确认1项:新增subagent_result没有完成child分类,也未纳入现有tool surface,违反已有工具归属契约。请在现有表中补齐,不要新增一层权限系统。

Spec

3项已复现P2:

  1. Darwin使用/dev/fd/目录句柄拼子路径,macOS上的cache读写失败。
  2. 超过16KiB的单行结果无法通过line offset/limit恢复;limit=1仍只有截断提示。
  3. truncateUtf8Head把四字节字符的3字节前缀解码为替换字符,却误判为完整UTF-8前缀。

详见行内。它们直接影响本PR承诺的可恢复性、兼容性和UTF-8正确性;无需扩展成新存储框架。

验证

Node24/macOS exact checkout:check因index.ts/manager.ts格式失败;完整test失败,明确包含child drift guard和Mac artifact错误。独立重跑guard仍报subagent_result未分类;Mac artifact专项仍报ENOENT /dev/fd/.../cache。独立pageResultText复现20KiB单行只返回60-byte提示;truncateUtf8Head("😀",3)返回替换字符,4096字节真实预算同样复现。GitHub Node22/24 CI也为失败。不把其他并行运行出现的setup超时直接归因本PR;未做实际Pi UI或发布验收。

请修复这些边界并跑通check/test后再复审。本轮未修改源码、提交或合并。

});

pi.registerTool({
name: "subagent_result",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 将新工具纳入现有归属和 child 权限分类

新增 subagent_result 后,extensions/shared/child-session.ts 的 child-safe/excluded 列表都没有它,OPENPI_TOOL_SURFACE.subagents 也未登记。当前现有 every registered package tool is classified ... 测试稳定失败,明确报该工具未分类。请按真实用途加入已有 parent-only/child-safe 分类及 subagents 工具可见性表,验证关闭能力和子会话边界,不应让新工具绕开已建立的登记契约。

function descriptorRelativePath(fd: number, fallback: string) {
if (process.platform === "linux") return `/proc/self/fd/${fd}`;
if (
process.platform === "darwin" ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Darwin 的 /dev/fd 不能当作 Linux 的目录遍历入口

本机 macOS 实测目录fd本身可fstat,/dev/fd/<fd>也显示directory,但访问/dev/fd/<fd>/package.json即ENOENT;它不像Linux /proc/self/fd/<fd>支持继续拼接子路径。因此这里声明支持darwin后,openCheckedDirectory中的mkdir/read始终失败,artifact专项稳定报ENOENT mkdir /dev/fd/14/cache,不是测试并行噪声。请验证Mac可用、具有同等安全边界的实现;如果决定暂不支持,需在I/O前明确fail-closed并披露精确恢复的退化,不能仅跳过测试或声称兼容性未变。未推断其他BSD行为。

truncated: false,
};
}
const suffix = `\n[page truncated at ${maxBytes} bytes; reduce the requested range]`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 为超长单行保留可继续读取的恢复路径

当前工具只有行offset/limit,超出字节预算后使用的truncateHead不会截取首条超长行的部分内容。真实输入20KiB单行+尾标记,offset0/limit200与offset0/limit1都只返回60字节的截断提示;offset1已经EOF。用户按提示reduce range也无法取得正文,违反exact recovery用途。请提供最小行内继续读取方式或等价受保护恢复路径,同时保持页面字节上限,并补单行超限的完整恢复测试。

Comment threadextensions/subagents/src/snapshot.ts Outdated
let end = maxBytes;
while (end > 0) {
const candidate = bytes.subarray(0, end).toString("utf8");
if (byteLength(candidate) === end) return candidate;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 解码后字节数相同不代表 UTF-8 边界完整

truncateUtf8Head("😀", 3)实际返回U+FFFD替换字符:四字节emoji的前三字节解码成一个三字节replacement,恰好满足byteLength(candidate) === end,因此提前返回了并非原文前缀的字符串。真实4096预算下,4093个ASCII字符后接emoji也会出现同样损坏。现测试只有三字节中文,漏掉了此情形。请按codepoint边界回退或采用严格解码验证,并覆盖四字节字符各切割位置;不要让有界裁剪改写原文内容。

@tt-a1i

Copy link
Copy Markdown
Collaborator

当前 head b3d7d46 已复审,Changes Requested。旧的缓存失败阻塞settlement、无缓存上限、canonical/projection混用已有实质修复;新确认4项P2:工具分类遗漏、Mac缓存路径不可用、超长单行无法恢复、四字节UTF-8截断产生替换字符。check/test及必需CI当前均非绿色,详见 #238 (review) 。建议在现有边界内逐项修,不增加新框架。本轮未改源码或合并。

Keep canonical settlement separate from bounded projection, persist artifacts fail-soft, and recover through subagent_result(id, offset, limit) instead of cache pathnames.
Keep exact-result paging, UTF-8 truncation, Darwin cache paths, and
child-tool classification within the existing subagent surface.
@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from ba54ec5 to 781775eCompareSeptember 1, 2026 04:34

@tt-a1itt-a1i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

复审 exact head 781775e9acf419d34bff904c1e1353229251202e。目标是给 Direct Subagent snapshot 建立聚合 UTF-8 硬上限,同时保留 exact result 的恢复能力;价值是避免长 Session 的模型/UI read model 持续膨胀。当前提交已经修好工具分类、四字节 UTF-8 截断和超长单行分页,但仍有跨平台恢复、fail-open 和生命周期问题。

Standards

2 项:projection rebuild 失败会回退到完整 canonical snapshot,硬资源上限 fail-open;artifact persistence 用未受 Session Scope 管理的裸 setTimeout,shutdown 不取消/等待,且测试把 onSettled 内断言异常吞掉了。

Spec

2 项:exact artifact 现在仅支持 Linux,macOS 上超过 1 MiB 的终态结果不可恢复;Issue #180 要求的旧版/超大 persisted snapshot restore 边界仍没有实现,当前 projection 只存在于进程内 manager。另有 line-offset 转 byte cursor 时文本提示的 cursor 不是绝对值。

验证:macOS exact checkout bun run check 通过;manager/snapshot/result-artifact 专项 47 passed、14 个 Linux-only skipped;独立分页复现得到文本 next 38,结构化 absolute nextByteOffset=45。GitHub Node 22/24 仍各失败 2 项,因此必需 gate 未通过。建议先缩小并闭合现有边界,不要继续扩充存储框架。

const RESULT_CACHE_RECOVERY_NAME =
/^\.retention-lock\.recovery\.[0-9a-f-]{36}$/iu;
const RESULT_ARTIFACT_TEMP_NAME = /^\.[a-f0-9]{64}\.[0-9a-f-]{36}\.tmp$/u;
const SUPPORTED_DESCRIPTOR_PLATFORMS = new Set(["linux"]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 这不是修复 Darwin cache,而是把 exact-result artifact 限定为 Linux。macOS 上 persist 会抛出 unsupported,read 会变成 miss;manager 又只保留 1 MiB finalText,所以更大的 settled result 会被 subagent_result 明确判为 exact unavailable。Issue #180 要求 exact result artifacts 保持可恢复,PR body 也未披露 Linux-only。最小处理可以明确收窄 PR/Issue 范围并保留 #180 未完成;若仍声称 Fixes #180,则需要经验证的 Pi-native/平台安全恢复机制。

return true;
} catch {
// Never expose a projection from an older lifecycle state after a
// failed rebuild; readers fall back to the canonical snapshot.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 投影重建失败时清空所有 bounded projection,而 view.list/get 随后用 entry.projection ?? entry.snapshot 回退到完整 canonical snapshot。资源上限自身一旦失败,模型/UI 就收到未受 aggregate budget 限制的数据,属于 fail-open。请保留最后一个安全 projection 或返回明确的有界失败状态,不能把 canonical full snapshot 当普通 reader fallback。

// Artifact persistence is optional recovery work. Schedule it only
// after settlement has notified waiters and hooks, so a slow or broken
// writer can never hold the lifecycle path or a concurrency slot.
setTimeout(() => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 这个裸 setTimeout 不属于 manager Scope;disposeAll() 不取消也不等待它,所以 Session 关闭后仍可能写 cache、修改 entry 并触发 notify。它也保证 onSettled/delivery clone 发生在 artifact 关联之前。当前“persisted before settlement”测试在 onSettled 里抛出的断言会被生产代码 catch 掉,因此是假阳性。请跟踪并在 teardown 取消/有界等待该任务,同时用不会被生产 catch 吞掉的外部观测验证顺序。

);
}

const noticeFor = (next: number) => `\n[page truncated; next ${next}]`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] line-offset 页面截断时,这个 notice 使用 page-local end,但返回的 nextByteOffset 是加上 pageStartByte 的绝对 cursor。复现 prefix\n + 长行、offset:1,maxBytes:64:正文提示 next 38,details 返回 nextByteOffset:45。Pi 提供给模型的可靠正文不能指向另一个位置;请让提示使用 absolute cursor,或删掉正文中的数值并只暴露一个可消费的权威 cursor。

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:subagentsSubagent delegation, skills, or tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

subagents: 聚合 snapshot 缺少统一 byte 上限

3 participants

@smf-h@tt-a1i@somewan820
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(subagents): enforce UTF-8 snapshot budgets - #238

Open
smf-h wants to merge 4 commits into
openpi-dev:mainfrom
smf-h:fix/subagents-snapshot-180
Open

fix(subagents): enforce UTF-8 snapshot budgets#238
smf-h wants to merge 4 commits into
openpi-dev:mainfrom
smf-h:fix/subagents-snapshot-180

Conversation

@smf-h

Copy link
Copy Markdown
Contributor

概要

  • 为 subagent snapshot 增加严格的 UTF-8 字节级预算。
  • 对多个 agent 进行公平裁剪,并保留生命周期身份、终态状态、错误信息和恢复所需引用。
  • 在展示投影前持久化精确 terminal result,读取时优先使用 artifact 内容。
  • 保留 raw active tool map,确保 ToolUpdate / ToolEnd 生命周期事件仍可关联。
  • 对 malformed、oversized 和非法 snapshot 输入 fail safely。
  • 增加 UTF-8 边界、公平裁剪、幂等投影、artifact precedence 和 tool lifecycle 回归测试。

验证

  • bun test tests/extensions/subagents/snapshot.test.ts tests/extensions/subagents/manager.test.ts tests/extensions/subagents/index.test.ts
  • bun run lint
  • bun run typecheck

Fixes#180

@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from 535806d to 3eb2568CompareAugust 28, 2026 18:07

@somewan820somewan820 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P1 Must-Fix:extensions/subagents/src/manager.ts:427-435 的 persistExactResult() 在 settle() 中执行,未处理 writer 错误;生产 persistResultArtifact() 可因权限、symlink、磁盘满或 collision 抛错,导致 settle() 在 notify()/onSettled() 前失败,waiter/result delivery 可能永远收不到终态。请让 artifact cache fail-soft,或保证 settlement 先终态通知再处理缓存失败,并补充 failure-path tests。

P1 Must-Fix:exact-result artifact 没有 retention/aggregate size limit,长期运行会持续积累唯一结果并耗尽 cache volume。请加入 bounded cleanup/retention 及写入/清理失败恢复。

P2 Should-Fix:enforceSnapshotBudget() 将有损 projection 写回 live entry.snapshot,transcript omits 后 takeover UI 无 rehydrate 路径,历史会静默消失。执行 snapshot 与 UI/model projection 应分离,或提供显式 rehydrate/omission 状态。P3:snapshot parsers 当前无生产调用方。静态审查,未运行测试。

@github-actionsgithub-actionsBot added the area:subagents Subagent delegation, skills, or tests label Aug 30, 2026
@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from 3a0cc46 to 51b6a22CompareAugust 30, 2026 08:34
@smf-h
smf-h requested a review from somewan820August 30, 2026 08:37
@smf-h

smf-h commented Aug 31, 2026

Copy link
Copy Markdown
ContributorAuthor

感谢审查,已在最新提交 中处理这些问题。

P1:artifact persistence 不再阻断 settlement

 settle() 现在先完成 terminal status、retained result、projection 更新、 notify() 、 onSettled() 、waiter 唤醒及并发槽释放,再将 artifact persistence 调度为异步的可选恢复工作。因此权限错误、symlink、磁盘满、collision、lock contention 或 writer throw 都不会阻止终态通知,也不会让 waiter/result delivery 永久等待。

延迟 writer 会捕获 cache/filesystem 异常,严格验证 ref 只能是 { version: 1, digest } ,验证 digest 与 exact result 内容匹配,并使用 run generation guard 防止旧 run 的延迟 writer 把 artifact 附加到重启后的新 run。失败时不会回滚 terminal status 或 finalText 。

新增/更新的 manager failure‑path tests 验证了 writer throw、wrong digest 和非法 ref 场景下: waitFor() 返回、终态可从 per‑id listener 观察、 onSettled() 被调用,且后续仍可 spawn。

P1:artifact cache bounded retention

cache 现在有硬性 retention 上限:最多 64 个 artifact 文件、最多 64 MiB payload;自定义 maxFiles / maxBytes 不能超过 hard cap。retention eviction 和 stale metadata cleanup 在 cache lock 内完成。超大写入在触碰 cache 前拒绝,不留下 partial artifact 或 .tmp ,后续正常写入仍可继续。

锁获取使用有界重试;ownership 不确定、PID reuse、lock contention、symlink、unknown/recovery entry 或平台无法提供安全 no‑follow 操作时均 fail‑closed。release cleanup 失败也不会把已成功发布的 ref 变成失败。

P2:canonical snapshot 与 projection 分离

 entry.snapshot 保持 canonical event‑folding state, entry.projection 是独立的 aggregate‑bounded read model;projection 不再写回 canonical snapshot。takeover 继续使用 view.getFull?.(id) ?? view.get(id) 。

projection rebuild 失败时会清除旧 projection,读取方回退到最新 canonical state,避免 settled 条目继续显示旧的 running 。exact‑result recovery 只使用经过校验的 artifact 或明确完整的 canonical retained finalText ,不会把 projection 当作 recovery source。新增 finalTextTruncated 标记:当 artifact 不可用且 retained text 被 1 MiB cap 截断时, subagent_result 会明确报告 exact result unavailable,而不是返回截断前缀。

P3:snapshot projection 已有生产调用方

manager 会在 entry 注册和 lifecycle 更新时调用 snapshot projection;canonical snapshot 仍供 event folding 和 takeover rehydration 使用,model‑facing 工具使用 bounded projection。

验证

已执行:

text

node --test --experimental-strip-types tests/extensions/subagents/manager.test.ts tests/extensions/subagents/result-artifact.test.ts tests/extensions/subagents/snapshot.test.ts tests/extensions/subagents/takeover.test.ts tests/extensions/subagents/index.test.ts

结果:88 tests,74 passed,0 failed,14 skipped。

另已执行并通过:

text

bun run typecheck
bun run lint

14 个 skipped 测试依赖 POSIX descriptor‑relative/no‑follow 文件系统能力;当前环境为 Windows,因此按预期跳过。Windows fail‑closed 测试已通过。POSIX lock recovery、并发 retention 和 symlink cache 行为未能在当前 Windows 环境实际执行,未修改 skip 条件或削弱安全行为。

@tt-a1itt-a1i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

复审 exact head b3d7d4665f160e0e3eb8fcaba73c27f536025885。目标是用UTF-8字节预算限制子代理展示快照,同时通过canonical state/受保护artifact保留恢复能力。旧review中的settlement被缓存异常阻塞、缓存无上限、展示投影覆盖canonical state三项都有实质改进,不重复计入。

Standards

独立人工审查无额外架构要求;现有自动守卫确认1项:新增subagent_result没有完成child分类,也未纳入现有tool surface,违反已有工具归属契约。请在现有表中补齐,不要新增一层权限系统。

Spec

3项已复现P2:

  1. Darwin使用/dev/fd/目录句柄拼子路径,macOS上的cache读写失败。
  2. 超过16KiB的单行结果无法通过line offset/limit恢复;limit=1仍只有截断提示。
  3. truncateUtf8Head把四字节字符的3字节前缀解码为替换字符,却误判为完整UTF-8前缀。

详见行内。它们直接影响本PR承诺的可恢复性、兼容性和UTF-8正确性;无需扩展成新存储框架。

验证

Node24/macOS exact checkout:check因index.ts/manager.ts格式失败;完整test失败,明确包含child drift guard和Mac artifact错误。独立重跑guard仍报subagent_result未分类;Mac artifact专项仍报ENOENT /dev/fd/.../cache。独立pageResultText复现20KiB单行只返回60-byte提示;truncateUtf8Head("😀",3)返回替换字符,4096字节真实预算同样复现。GitHub Node22/24 CI也为失败。不把其他并行运行出现的setup超时直接归因本PR;未做实际Pi UI或发布验收。

请修复这些边界并跑通check/test后再复审。本轮未修改源码、提交或合并。

});

pi.registerTool({
name: "subagent_result",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 将新工具纳入现有归属和 child 权限分类

新增 subagent_result 后,extensions/shared/child-session.ts 的 child-safe/excluded 列表都没有它,OPENPI_TOOL_SURFACE.subagents 也未登记。当前现有 every registered package tool is classified ... 测试稳定失败,明确报该工具未分类。请按真实用途加入已有 parent-only/child-safe 分类及 subagents 工具可见性表,验证关闭能力和子会话边界,不应让新工具绕开已建立的登记契约。

function descriptorRelativePath(fd: number, fallback: string) {
if (process.platform === "linux") return `/proc/self/fd/${fd}`;
if (
process.platform === "darwin" ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Darwin 的 /dev/fd 不能当作 Linux 的目录遍历入口

本机 macOS 实测目录fd本身可fstat,/dev/fd/<fd>也显示directory,但访问/dev/fd/<fd>/package.json即ENOENT;它不像Linux /proc/self/fd/<fd>支持继续拼接子路径。因此这里声明支持darwin后,openCheckedDirectory中的mkdir/read始终失败,artifact专项稳定报ENOENT mkdir /dev/fd/14/cache,不是测试并行噪声。请验证Mac可用、具有同等安全边界的实现;如果决定暂不支持,需在I/O前明确fail-closed并披露精确恢复的退化,不能仅跳过测试或声称兼容性未变。未推断其他BSD行为。

truncated: false,
};
}
const suffix = `\n[page truncated at ${maxBytes} bytes; reduce the requested range]`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 为超长单行保留可继续读取的恢复路径

当前工具只有行offset/limit,超出字节预算后使用的truncateHead不会截取首条超长行的部分内容。真实输入20KiB单行+尾标记,offset0/limit200与offset0/limit1都只返回60字节的截断提示;offset1已经EOF。用户按提示reduce range也无法取得正文,违反exact recovery用途。请提供最小行内继续读取方式或等价受保护恢复路径,同时保持页面字节上限,并补单行超限的完整恢复测试。

Comment threadextensions/subagents/src/snapshot.ts Outdated
let end = maxBytes;
while (end > 0) {
const candidate = bytes.subarray(0, end).toString("utf8");
if (byteLength(candidate) === end) return candidate;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 解码后字节数相同不代表 UTF-8 边界完整

truncateUtf8Head("😀", 3)实际返回U+FFFD替换字符:四字节emoji的前三字节解码成一个三字节replacement,恰好满足byteLength(candidate) === end,因此提前返回了并非原文前缀的字符串。真实4096预算下,4093个ASCII字符后接emoji也会出现同样损坏。现测试只有三字节中文,漏掉了此情形。请按codepoint边界回退或采用严格解码验证,并覆盖四字节字符各切割位置;不要让有界裁剪改写原文内容。

@tt-a1i

Copy link
Copy Markdown
Collaborator

当前 head b3d7d46 已复审,Changes Requested。旧的缓存失败阻塞settlement、无缓存上限、canonical/projection混用已有实质修复;新确认4项P2:工具分类遗漏、Mac缓存路径不可用、超长单行无法恢复、四字节UTF-8截断产生替换字符。check/test及必需CI当前均非绿色,详见 #238 (review) 。建议在现有边界内逐项修,不增加新框架。本轮未改源码或合并。

Keep canonical settlement separate from bounded projection, persist artifacts fail-soft, and recover through subagent_result(id, offset, limit) instead of cache pathnames.
Keep exact-result paging, UTF-8 truncation, Darwin cache paths, and
child-tool classification within the existing subagent surface.
@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from ba54ec5 to 781775eCompareSeptember 1, 2026 04:34

@tt-a1itt-a1i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

复审 exact head 781775e9acf419d34bff904c1e1353229251202e。目标是给 Direct Subagent snapshot 建立聚合 UTF-8 硬上限,同时保留 exact result 的恢复能力;价值是避免长 Session 的模型/UI read model 持续膨胀。当前提交已经修好工具分类、四字节 UTF-8 截断和超长单行分页,但仍有跨平台恢复、fail-open 和生命周期问题。

Standards

2 项:projection rebuild 失败会回退到完整 canonical snapshot,硬资源上限 fail-open;artifact persistence 用未受 Session Scope 管理的裸 setTimeout,shutdown 不取消/等待,且测试把 onSettled 内断言异常吞掉了。

Spec

2 项:exact artifact 现在仅支持 Linux,macOS 上超过 1 MiB 的终态结果不可恢复;Issue #180 要求的旧版/超大 persisted snapshot restore 边界仍没有实现,当前 projection 只存在于进程内 manager。另有 line-offset 转 byte cursor 时文本提示的 cursor 不是绝对值。

验证:macOS exact checkout bun run check 通过;manager/snapshot/result-artifact 专项 47 passed、14 个 Linux-only skipped;独立分页复现得到文本 next 38,结构化 absolute nextByteOffset=45。GitHub Node 22/24 仍各失败 2 项,因此必需 gate 未通过。建议先缩小并闭合现有边界,不要继续扩充存储框架。

const RESULT_CACHE_RECOVERY_NAME =
/^\.retention-lock\.recovery\.[0-9a-f-]{36}$/iu;
const RESULT_ARTIFACT_TEMP_NAME = /^\.[a-f0-9]{64}\.[0-9a-f-]{36}\.tmp$/u;
const SUPPORTED_DESCRIPTOR_PLATFORMS = new Set(["linux"]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 这不是修复 Darwin cache,而是把 exact-result artifact 限定为 Linux。macOS 上 persist 会抛出 unsupported,read 会变成 miss;manager 又只保留 1 MiB finalText,所以更大的 settled result 会被 subagent_result 明确判为 exact unavailable。Issue #180 要求 exact result artifacts 保持可恢复,PR body 也未披露 Linux-only。最小处理可以明确收窄 PR/Issue 范围并保留 #180 未完成;若仍声称 Fixes #180,则需要经验证的 Pi-native/平台安全恢复机制。

return true;
} catch {
// Never expose a projection from an older lifecycle state after a
// failed rebuild; readers fall back to the canonical snapshot.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 投影重建失败时清空所有 bounded projection,而 view.list/get 随后用 entry.projection ?? entry.snapshot 回退到完整 canonical snapshot。资源上限自身一旦失败,模型/UI 就收到未受 aggregate budget 限制的数据,属于 fail-open。请保留最后一个安全 projection 或返回明确的有界失败状态,不能把 canonical full snapshot 当普通 reader fallback。

// Artifact persistence is optional recovery work. Schedule it only
// after settlement has notified waiters and hooks, so a slow or broken
// writer can never hold the lifecycle path or a concurrency slot.
setTimeout(() => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 这个裸 setTimeout 不属于 manager Scope;disposeAll() 不取消也不等待它,所以 Session 关闭后仍可能写 cache、修改 entry 并触发 notify。它也保证 onSettled/delivery clone 发生在 artifact 关联之前。当前“persisted before settlement”测试在 onSettled 里抛出的断言会被生产代码 catch 掉,因此是假阳性。请跟踪并在 teardown 取消/有界等待该任务,同时用不会被生产 catch 吞掉的外部观测验证顺序。

);
}

const noticeFor = (next: number) => `\n[page truncated; next ${next}]`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] line-offset 页面截断时,这个 notice 使用 page-local end,但返回的 nextByteOffset 是加上 pageStartByte 的绝对 cursor。复现 prefix\n + 长行、offset:1,maxBytes:64:正文提示 next 38,details 返回 nextByteOffset:45。Pi 提供给模型的可靠正文不能指向另一个位置;请让提示使用 absolute cursor,或删掉正文中的数值并只暴露一个可消费的权威 cursor。

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:subagentsSubagent delegation, skills, or tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

subagents: 聚合 snapshot 缺少统一 byte 上限

3 participants

@smf-h@tt-a1i@somewan820
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(subagents): enforce UTF-8 snapshot budgets - #238

Open
smf-h wants to merge 4 commits into
openpi-dev:mainfrom
smf-h:fix/subagents-snapshot-180
Open

fix(subagents): enforce UTF-8 snapshot budgets#238
smf-h wants to merge 4 commits into
openpi-dev:mainfrom
smf-h:fix/subagents-snapshot-180

Conversation

@smf-h

Copy link
Copy Markdown
Contributor

概要

  • 为 subagent snapshot 增加严格的 UTF-8 字节级预算。
  • 对多个 agent 进行公平裁剪,并保留生命周期身份、终态状态、错误信息和恢复所需引用。
  • 在展示投影前持久化精确 terminal result,读取时优先使用 artifact 内容。
  • 保留 raw active tool map,确保 ToolUpdate / ToolEnd 生命周期事件仍可关联。
  • 对 malformed、oversized 和非法 snapshot 输入 fail safely。
  • 增加 UTF-8 边界、公平裁剪、幂等投影、artifact precedence 和 tool lifecycle 回归测试。

验证

  • bun test tests/extensions/subagents/snapshot.test.ts tests/extensions/subagents/manager.test.ts tests/extensions/subagents/index.test.ts
  • bun run lint
  • bun run typecheck

Fixes#180

@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from 535806d to 3eb2568CompareAugust 28, 2026 18:07

@somewan820somewan820 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P1 Must-Fix:extensions/subagents/src/manager.ts:427-435 的 persistExactResult() 在 settle() 中执行,未处理 writer 错误;生产 persistResultArtifact() 可因权限、symlink、磁盘满或 collision 抛错,导致 settle() 在 notify()/onSettled() 前失败,waiter/result delivery 可能永远收不到终态。请让 artifact cache fail-soft,或保证 settlement 先终态通知再处理缓存失败,并补充 failure-path tests。

P1 Must-Fix:exact-result artifact 没有 retention/aggregate size limit,长期运行会持续积累唯一结果并耗尽 cache volume。请加入 bounded cleanup/retention 及写入/清理失败恢复。

P2 Should-Fix:enforceSnapshotBudget() 将有损 projection 写回 live entry.snapshot,transcript omits 后 takeover UI 无 rehydrate 路径,历史会静默消失。执行 snapshot 与 UI/model projection 应分离,或提供显式 rehydrate/omission 状态。P3:snapshot parsers 当前无生产调用方。静态审查,未运行测试。

@github-actionsgithub-actionsBot added the area:subagents Subagent delegation, skills, or tests label Aug 30, 2026
@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from 3a0cc46 to 51b6a22CompareAugust 30, 2026 08:34
@smf-h
smf-h requested a review from somewan820August 30, 2026 08:37
@smf-h

smf-h commented Aug 31, 2026

Copy link
Copy Markdown
ContributorAuthor

感谢审查,已在最新提交 中处理这些问题。

P1:artifact persistence 不再阻断 settlement

 settle() 现在先完成 terminal status、retained result、projection 更新、 notify() 、 onSettled() 、waiter 唤醒及并发槽释放,再将 artifact persistence 调度为异步的可选恢复工作。因此权限错误、symlink、磁盘满、collision、lock contention 或 writer throw 都不会阻止终态通知,也不会让 waiter/result delivery 永久等待。

延迟 writer 会捕获 cache/filesystem 异常,严格验证 ref 只能是 { version: 1, digest } ,验证 digest 与 exact result 内容匹配,并使用 run generation guard 防止旧 run 的延迟 writer 把 artifact 附加到重启后的新 run。失败时不会回滚 terminal status 或 finalText 。

新增/更新的 manager failure‑path tests 验证了 writer throw、wrong digest 和非法 ref 场景下: waitFor() 返回、终态可从 per‑id listener 观察、 onSettled() 被调用,且后续仍可 spawn。

P1:artifact cache bounded retention

cache 现在有硬性 retention 上限:最多 64 个 artifact 文件、最多 64 MiB payload;自定义 maxFiles / maxBytes 不能超过 hard cap。retention eviction 和 stale metadata cleanup 在 cache lock 内完成。超大写入在触碰 cache 前拒绝,不留下 partial artifact 或 .tmp ,后续正常写入仍可继续。

锁获取使用有界重试;ownership 不确定、PID reuse、lock contention、symlink、unknown/recovery entry 或平台无法提供安全 no‑follow 操作时均 fail‑closed。release cleanup 失败也不会把已成功发布的 ref 变成失败。

P2:canonical snapshot 与 projection 分离

 entry.snapshot 保持 canonical event‑folding state, entry.projection 是独立的 aggregate‑bounded read model;projection 不再写回 canonical snapshot。takeover 继续使用 view.getFull?.(id) ?? view.get(id) 。

projection rebuild 失败时会清除旧 projection,读取方回退到最新 canonical state,避免 settled 条目继续显示旧的 running 。exact‑result recovery 只使用经过校验的 artifact 或明确完整的 canonical retained finalText ,不会把 projection 当作 recovery source。新增 finalTextTruncated 标记:当 artifact 不可用且 retained text 被 1 MiB cap 截断时, subagent_result 会明确报告 exact result unavailable,而不是返回截断前缀。

P3:snapshot projection 已有生产调用方

manager 会在 entry 注册和 lifecycle 更新时调用 snapshot projection;canonical snapshot 仍供 event folding 和 takeover rehydration 使用,model‑facing 工具使用 bounded projection。

验证

已执行:

text

node --test --experimental-strip-types tests/extensions/subagents/manager.test.ts tests/extensions/subagents/result-artifact.test.ts tests/extensions/subagents/snapshot.test.ts tests/extensions/subagents/takeover.test.ts tests/extensions/subagents/index.test.ts

结果:88 tests,74 passed,0 failed,14 skipped。

另已执行并通过:

text

bun run typecheck
bun run lint

14 个 skipped 测试依赖 POSIX descriptor‑relative/no‑follow 文件系统能力;当前环境为 Windows,因此按预期跳过。Windows fail‑closed 测试已通过。POSIX lock recovery、并发 retention 和 symlink cache 行为未能在当前 Windows 环境实际执行,未修改 skip 条件或削弱安全行为。

@tt-a1itt-a1i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

复审 exact head b3d7d4665f160e0e3eb8fcaba73c27f536025885。目标是用UTF-8字节预算限制子代理展示快照,同时通过canonical state/受保护artifact保留恢复能力。旧review中的settlement被缓存异常阻塞、缓存无上限、展示投影覆盖canonical state三项都有实质改进,不重复计入。

Standards

独立人工审查无额外架构要求;现有自动守卫确认1项:新增subagent_result没有完成child分类,也未纳入现有tool surface,违反已有工具归属契约。请在现有表中补齐,不要新增一层权限系统。

Spec

3项已复现P2:

  1. Darwin使用/dev/fd/目录句柄拼子路径,macOS上的cache读写失败。
  2. 超过16KiB的单行结果无法通过line offset/limit恢复;limit=1仍只有截断提示。
  3. truncateUtf8Head把四字节字符的3字节前缀解码为替换字符,却误判为完整UTF-8前缀。

详见行内。它们直接影响本PR承诺的可恢复性、兼容性和UTF-8正确性;无需扩展成新存储框架。

验证

Node24/macOS exact checkout:check因index.ts/manager.ts格式失败;完整test失败,明确包含child drift guard和Mac artifact错误。独立重跑guard仍报subagent_result未分类;Mac artifact专项仍报ENOENT /dev/fd/.../cache。独立pageResultText复现20KiB单行只返回60-byte提示;truncateUtf8Head("😀",3)返回替换字符,4096字节真实预算同样复现。GitHub Node22/24 CI也为失败。不把其他并行运行出现的setup超时直接归因本PR;未做实际Pi UI或发布验收。

请修复这些边界并跑通check/test后再复审。本轮未修改源码、提交或合并。

});

pi.registerTool({
name: "subagent_result",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 将新工具纳入现有归属和 child 权限分类

新增 subagent_result 后,extensions/shared/child-session.ts 的 child-safe/excluded 列表都没有它,OPENPI_TOOL_SURFACE.subagents 也未登记。当前现有 every registered package tool is classified ... 测试稳定失败,明确报该工具未分类。请按真实用途加入已有 parent-only/child-safe 分类及 subagents 工具可见性表,验证关闭能力和子会话边界,不应让新工具绕开已建立的登记契约。

function descriptorRelativePath(fd: number, fallback: string) {
if (process.platform === "linux") return `/proc/self/fd/${fd}`;
if (
process.platform === "darwin" ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Darwin 的 /dev/fd 不能当作 Linux 的目录遍历入口

本机 macOS 实测目录fd本身可fstat,/dev/fd/<fd>也显示directory,但访问/dev/fd/<fd>/package.json即ENOENT;它不像Linux /proc/self/fd/<fd>支持继续拼接子路径。因此这里声明支持darwin后,openCheckedDirectory中的mkdir/read始终失败,artifact专项稳定报ENOENT mkdir /dev/fd/14/cache,不是测试并行噪声。请验证Mac可用、具有同等安全边界的实现;如果决定暂不支持,需在I/O前明确fail-closed并披露精确恢复的退化,不能仅跳过测试或声称兼容性未变。未推断其他BSD行为。

truncated: false,
};
}
const suffix = `\n[page truncated at ${maxBytes} bytes; reduce the requested range]`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 为超长单行保留可继续读取的恢复路径

当前工具只有行offset/limit,超出字节预算后使用的truncateHead不会截取首条超长行的部分内容。真实输入20KiB单行+尾标记,offset0/limit200与offset0/limit1都只返回60字节的截断提示;offset1已经EOF。用户按提示reduce range也无法取得正文,违反exact recovery用途。请提供最小行内继续读取方式或等价受保护恢复路径,同时保持页面字节上限,并补单行超限的完整恢复测试。

Comment threadextensions/subagents/src/snapshot.ts Outdated
let end = maxBytes;
while (end > 0) {
const candidate = bytes.subarray(0, end).toString("utf8");
if (byteLength(candidate) === end) return candidate;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 解码后字节数相同不代表 UTF-8 边界完整

truncateUtf8Head("😀", 3)实际返回U+FFFD替换字符:四字节emoji的前三字节解码成一个三字节replacement,恰好满足byteLength(candidate) === end,因此提前返回了并非原文前缀的字符串。真实4096预算下,4093个ASCII字符后接emoji也会出现同样损坏。现测试只有三字节中文,漏掉了此情形。请按codepoint边界回退或采用严格解码验证,并覆盖四字节字符各切割位置;不要让有界裁剪改写原文内容。

@tt-a1i

Copy link
Copy Markdown
Collaborator

当前 head b3d7d46 已复审,Changes Requested。旧的缓存失败阻塞settlement、无缓存上限、canonical/projection混用已有实质修复;新确认4项P2:工具分类遗漏、Mac缓存路径不可用、超长单行无法恢复、四字节UTF-8截断产生替换字符。check/test及必需CI当前均非绿色,详见 #238 (review) 。建议在现有边界内逐项修,不增加新框架。本轮未改源码或合并。

Keep canonical settlement separate from bounded projection, persist artifacts fail-soft, and recover through subagent_result(id, offset, limit) instead of cache pathnames.
Keep exact-result paging, UTF-8 truncation, Darwin cache paths, and
child-tool classification within the existing subagent surface.
@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from ba54ec5 to 781775eCompareSeptember 1, 2026 04:34

@tt-a1itt-a1i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

复审 exact head 781775e9acf419d34bff904c1e1353229251202e。目标是给 Direct Subagent snapshot 建立聚合 UTF-8 硬上限,同时保留 exact result 的恢复能力;价值是避免长 Session 的模型/UI read model 持续膨胀。当前提交已经修好工具分类、四字节 UTF-8 截断和超长单行分页,但仍有跨平台恢复、fail-open 和生命周期问题。

Standards

2 项:projection rebuild 失败会回退到完整 canonical snapshot,硬资源上限 fail-open;artifact persistence 用未受 Session Scope 管理的裸 setTimeout,shutdown 不取消/等待,且测试把 onSettled 内断言异常吞掉了。

Spec

2 项:exact artifact 现在仅支持 Linux,macOS 上超过 1 MiB 的终态结果不可恢复;Issue #180 要求的旧版/超大 persisted snapshot restore 边界仍没有实现,当前 projection 只存在于进程内 manager。另有 line-offset 转 byte cursor 时文本提示的 cursor 不是绝对值。

验证:macOS exact checkout bun run check 通过;manager/snapshot/result-artifact 专项 47 passed、14 个 Linux-only skipped;独立分页复现得到文本 next 38,结构化 absolute nextByteOffset=45。GitHub Node 22/24 仍各失败 2 项,因此必需 gate 未通过。建议先缩小并闭合现有边界,不要继续扩充存储框架。

const RESULT_CACHE_RECOVERY_NAME =
/^\.retention-lock\.recovery\.[0-9a-f-]{36}$/iu;
const RESULT_ARTIFACT_TEMP_NAME = /^\.[a-f0-9]{64}\.[0-9a-f-]{36}\.tmp$/u;
const SUPPORTED_DESCRIPTOR_PLATFORMS = new Set(["linux"]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 这不是修复 Darwin cache,而是把 exact-result artifact 限定为 Linux。macOS 上 persist 会抛出 unsupported,read 会变成 miss;manager 又只保留 1 MiB finalText,所以更大的 settled result 会被 subagent_result 明确判为 exact unavailable。Issue #180 要求 exact result artifacts 保持可恢复,PR body 也未披露 Linux-only。最小处理可以明确收窄 PR/Issue 范围并保留 #180 未完成;若仍声称 Fixes #180,则需要经验证的 Pi-native/平台安全恢复机制。

return true;
} catch {
// Never expose a projection from an older lifecycle state after a
// failed rebuild; readers fall back to the canonical snapshot.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 投影重建失败时清空所有 bounded projection,而 view.list/get 随后用 entry.projection ?? entry.snapshot 回退到完整 canonical snapshot。资源上限自身一旦失败,模型/UI 就收到未受 aggregate budget 限制的数据,属于 fail-open。请保留最后一个安全 projection 或返回明确的有界失败状态,不能把 canonical full snapshot 当普通 reader fallback。

// Artifact persistence is optional recovery work. Schedule it only
// after settlement has notified waiters and hooks, so a slow or broken
// writer can never hold the lifecycle path or a concurrency slot.
setTimeout(() => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 这个裸 setTimeout 不属于 manager Scope;disposeAll() 不取消也不等待它,所以 Session 关闭后仍可能写 cache、修改 entry 并触发 notify。它也保证 onSettled/delivery clone 发生在 artifact 关联之前。当前“persisted before settlement”测试在 onSettled 里抛出的断言会被生产代码 catch 掉,因此是假阳性。请跟踪并在 teardown 取消/有界等待该任务,同时用不会被生产 catch 吞掉的外部观测验证顺序。

);
}

const noticeFor = (next: number) => `\n[page truncated; next ${next}]`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] line-offset 页面截断时,这个 notice 使用 page-local end,但返回的 nextByteOffset 是加上 pageStartByte 的绝对 cursor。复现 prefix\n + 长行、offset:1,maxBytes:64:正文提示 next 38,details 返回 nextByteOffset:45。Pi 提供给模型的可靠正文不能指向另一个位置;请让提示使用 absolute cursor,或删掉正文中的数值并只暴露一个可消费的权威 cursor。

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:subagentsSubagent delegation, skills, or tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

subagents: 聚合 snapshot 缺少统一 byte 上限

3 participants

@smf-h@tt-a1i@somewan820
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(subagents): enforce UTF-8 snapshot budgets - #238

Open
smf-h wants to merge 4 commits into
openpi-dev:mainfrom
smf-h:fix/subagents-snapshot-180
Open

fix(subagents): enforce UTF-8 snapshot budgets#238
smf-h wants to merge 4 commits into
openpi-dev:mainfrom
smf-h:fix/subagents-snapshot-180

Conversation

@smf-h

Copy link
Copy Markdown
Contributor

概要

  • 为 subagent snapshot 增加严格的 UTF-8 字节级预算。
  • 对多个 agent 进行公平裁剪,并保留生命周期身份、终态状态、错误信息和恢复所需引用。
  • 在展示投影前持久化精确 terminal result,读取时优先使用 artifact 内容。
  • 保留 raw active tool map,确保 ToolUpdate / ToolEnd 生命周期事件仍可关联。
  • 对 malformed、oversized 和非法 snapshot 输入 fail safely。
  • 增加 UTF-8 边界、公平裁剪、幂等投影、artifact precedence 和 tool lifecycle 回归测试。

验证

  • bun test tests/extensions/subagents/snapshot.test.ts tests/extensions/subagents/manager.test.ts tests/extensions/subagents/index.test.ts
  • bun run lint
  • bun run typecheck

Fixes#180

@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from 535806d to 3eb2568CompareAugust 28, 2026 18:07

@somewan820somewan820 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P1 Must-Fix:extensions/subagents/src/manager.ts:427-435 的 persistExactResult() 在 settle() 中执行,未处理 writer 错误;生产 persistResultArtifact() 可因权限、symlink、磁盘满或 collision 抛错,导致 settle() 在 notify()/onSettled() 前失败,waiter/result delivery 可能永远收不到终态。请让 artifact cache fail-soft,或保证 settlement 先终态通知再处理缓存失败,并补充 failure-path tests。

P1 Must-Fix:exact-result artifact 没有 retention/aggregate size limit,长期运行会持续积累唯一结果并耗尽 cache volume。请加入 bounded cleanup/retention 及写入/清理失败恢复。

P2 Should-Fix:enforceSnapshotBudget() 将有损 projection 写回 live entry.snapshot,transcript omits 后 takeover UI 无 rehydrate 路径,历史会静默消失。执行 snapshot 与 UI/model projection 应分离,或提供显式 rehydrate/omission 状态。P3:snapshot parsers 当前无生产调用方。静态审查,未运行测试。

@github-actionsgithub-actionsBot added the area:subagents Subagent delegation, skills, or tests label Aug 30, 2026
@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from 3a0cc46 to 51b6a22CompareAugust 30, 2026 08:34
@smf-h
smf-h requested a review from somewan820August 30, 2026 08:37
@smf-h

smf-h commented Aug 31, 2026

Copy link
Copy Markdown
ContributorAuthor

感谢审查,已在最新提交 中处理这些问题。

P1:artifact persistence 不再阻断 settlement

 settle() 现在先完成 terminal status、retained result、projection 更新、 notify() 、 onSettled() 、waiter 唤醒及并发槽释放,再将 artifact persistence 调度为异步的可选恢复工作。因此权限错误、symlink、磁盘满、collision、lock contention 或 writer throw 都不会阻止终态通知,也不会让 waiter/result delivery 永久等待。

延迟 writer 会捕获 cache/filesystem 异常,严格验证 ref 只能是 { version: 1, digest } ,验证 digest 与 exact result 内容匹配,并使用 run generation guard 防止旧 run 的延迟 writer 把 artifact 附加到重启后的新 run。失败时不会回滚 terminal status 或 finalText 。

新增/更新的 manager failure‑path tests 验证了 writer throw、wrong digest 和非法 ref 场景下: waitFor() 返回、终态可从 per‑id listener 观察、 onSettled() 被调用,且后续仍可 spawn。

P1:artifact cache bounded retention

cache 现在有硬性 retention 上限:最多 64 个 artifact 文件、最多 64 MiB payload;自定义 maxFiles / maxBytes 不能超过 hard cap。retention eviction 和 stale metadata cleanup 在 cache lock 内完成。超大写入在触碰 cache 前拒绝,不留下 partial artifact 或 .tmp ,后续正常写入仍可继续。

锁获取使用有界重试;ownership 不确定、PID reuse、lock contention、symlink、unknown/recovery entry 或平台无法提供安全 no‑follow 操作时均 fail‑closed。release cleanup 失败也不会把已成功发布的 ref 变成失败。

P2:canonical snapshot 与 projection 分离

 entry.snapshot 保持 canonical event‑folding state, entry.projection 是独立的 aggregate‑bounded read model;projection 不再写回 canonical snapshot。takeover 继续使用 view.getFull?.(id) ?? view.get(id) 。

projection rebuild 失败时会清除旧 projection,读取方回退到最新 canonical state,避免 settled 条目继续显示旧的 running 。exact‑result recovery 只使用经过校验的 artifact 或明确完整的 canonical retained finalText ,不会把 projection 当作 recovery source。新增 finalTextTruncated 标记:当 artifact 不可用且 retained text 被 1 MiB cap 截断时, subagent_result 会明确报告 exact result unavailable,而不是返回截断前缀。

P3:snapshot projection 已有生产调用方

manager 会在 entry 注册和 lifecycle 更新时调用 snapshot projection;canonical snapshot 仍供 event folding 和 takeover rehydration 使用,model‑facing 工具使用 bounded projection。

验证

已执行:

text

node --test --experimental-strip-types tests/extensions/subagents/manager.test.ts tests/extensions/subagents/result-artifact.test.ts tests/extensions/subagents/snapshot.test.ts tests/extensions/subagents/takeover.test.ts tests/extensions/subagents/index.test.ts

结果:88 tests,74 passed,0 failed,14 skipped。

另已执行并通过:

text

bun run typecheck
bun run lint

14 个 skipped 测试依赖 POSIX descriptor‑relative/no‑follow 文件系统能力;当前环境为 Windows,因此按预期跳过。Windows fail‑closed 测试已通过。POSIX lock recovery、并发 retention 和 symlink cache 行为未能在当前 Windows 环境实际执行,未修改 skip 条件或削弱安全行为。

@tt-a1itt-a1i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

复审 exact head b3d7d4665f160e0e3eb8fcaba73c27f536025885。目标是用UTF-8字节预算限制子代理展示快照,同时通过canonical state/受保护artifact保留恢复能力。旧review中的settlement被缓存异常阻塞、缓存无上限、展示投影覆盖canonical state三项都有实质改进,不重复计入。

Standards

独立人工审查无额外架构要求;现有自动守卫确认1项:新增subagent_result没有完成child分类,也未纳入现有tool surface,违反已有工具归属契约。请在现有表中补齐,不要新增一层权限系统。

Spec

3项已复现P2:

  1. Darwin使用/dev/fd/目录句柄拼子路径,macOS上的cache读写失败。
  2. 超过16KiB的单行结果无法通过line offset/limit恢复;limit=1仍只有截断提示。
  3. truncateUtf8Head把四字节字符的3字节前缀解码为替换字符,却误判为完整UTF-8前缀。

详见行内。它们直接影响本PR承诺的可恢复性、兼容性和UTF-8正确性;无需扩展成新存储框架。

验证

Node24/macOS exact checkout:check因index.ts/manager.ts格式失败;完整test失败,明确包含child drift guard和Mac artifact错误。独立重跑guard仍报subagent_result未分类;Mac artifact专项仍报ENOENT /dev/fd/.../cache。独立pageResultText复现20KiB单行只返回60-byte提示;truncateUtf8Head("😀",3)返回替换字符,4096字节真实预算同样复现。GitHub Node22/24 CI也为失败。不把其他并行运行出现的setup超时直接归因本PR;未做实际Pi UI或发布验收。

请修复这些边界并跑通check/test后再复审。本轮未修改源码、提交或合并。

});

pi.registerTool({
name: "subagent_result",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 将新工具纳入现有归属和 child 权限分类

新增 subagent_result 后,extensions/shared/child-session.ts 的 child-safe/excluded 列表都没有它,OPENPI_TOOL_SURFACE.subagents 也未登记。当前现有 every registered package tool is classified ... 测试稳定失败,明确报该工具未分类。请按真实用途加入已有 parent-only/child-safe 分类及 subagents 工具可见性表,验证关闭能力和子会话边界,不应让新工具绕开已建立的登记契约。

function descriptorRelativePath(fd: number, fallback: string) {
if (process.platform === "linux") return `/proc/self/fd/${fd}`;
if (
process.platform === "darwin" ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Darwin 的 /dev/fd 不能当作 Linux 的目录遍历入口

本机 macOS 实测目录fd本身可fstat,/dev/fd/<fd>也显示directory,但访问/dev/fd/<fd>/package.json即ENOENT;它不像Linux /proc/self/fd/<fd>支持继续拼接子路径。因此这里声明支持darwin后,openCheckedDirectory中的mkdir/read始终失败,artifact专项稳定报ENOENT mkdir /dev/fd/14/cache,不是测试并行噪声。请验证Mac可用、具有同等安全边界的实现;如果决定暂不支持,需在I/O前明确fail-closed并披露精确恢复的退化,不能仅跳过测试或声称兼容性未变。未推断其他BSD行为。

truncated: false,
};
}
const suffix = `\n[page truncated at ${maxBytes} bytes; reduce the requested range]`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 为超长单行保留可继续读取的恢复路径

当前工具只有行offset/limit,超出字节预算后使用的truncateHead不会截取首条超长行的部分内容。真实输入20KiB单行+尾标记,offset0/limit200与offset0/limit1都只返回60字节的截断提示;offset1已经EOF。用户按提示reduce range也无法取得正文,违反exact recovery用途。请提供最小行内继续读取方式或等价受保护恢复路径,同时保持页面字节上限,并补单行超限的完整恢复测试。

Comment threadextensions/subagents/src/snapshot.ts Outdated
let end = maxBytes;
while (end > 0) {
const candidate = bytes.subarray(0, end).toString("utf8");
if (byteLength(candidate) === end) return candidate;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 解码后字节数相同不代表 UTF-8 边界完整

truncateUtf8Head("😀", 3)实际返回U+FFFD替换字符:四字节emoji的前三字节解码成一个三字节replacement,恰好满足byteLength(candidate) === end,因此提前返回了并非原文前缀的字符串。真实4096预算下,4093个ASCII字符后接emoji也会出现同样损坏。现测试只有三字节中文,漏掉了此情形。请按codepoint边界回退或采用严格解码验证,并覆盖四字节字符各切割位置;不要让有界裁剪改写原文内容。

@tt-a1i

Copy link
Copy Markdown
Collaborator

当前 head b3d7d46 已复审,Changes Requested。旧的缓存失败阻塞settlement、无缓存上限、canonical/projection混用已有实质修复;新确认4项P2:工具分类遗漏、Mac缓存路径不可用、超长单行无法恢复、四字节UTF-8截断产生替换字符。check/test及必需CI当前均非绿色,详见 #238 (review) 。建议在现有边界内逐项修,不增加新框架。本轮未改源码或合并。

Keep canonical settlement separate from bounded projection, persist artifacts fail-soft, and recover through subagent_result(id, offset, limit) instead of cache pathnames.
Keep exact-result paging, UTF-8 truncation, Darwin cache paths, and
child-tool classification within the existing subagent surface.
@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from ba54ec5 to 781775eCompareSeptember 1, 2026 04:34

@tt-a1itt-a1i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

复审 exact head 781775e9acf419d34bff904c1e1353229251202e。目标是给 Direct Subagent snapshot 建立聚合 UTF-8 硬上限,同时保留 exact result 的恢复能力;价值是避免长 Session 的模型/UI read model 持续膨胀。当前提交已经修好工具分类、四字节 UTF-8 截断和超长单行分页,但仍有跨平台恢复、fail-open 和生命周期问题。

Standards

2 项:projection rebuild 失败会回退到完整 canonical snapshot,硬资源上限 fail-open;artifact persistence 用未受 Session Scope 管理的裸 setTimeout,shutdown 不取消/等待,且测试把 onSettled 内断言异常吞掉了。

Spec

2 项:exact artifact 现在仅支持 Linux,macOS 上超过 1 MiB 的终态结果不可恢复;Issue #180 要求的旧版/超大 persisted snapshot restore 边界仍没有实现,当前 projection 只存在于进程内 manager。另有 line-offset 转 byte cursor 时文本提示的 cursor 不是绝对值。

验证:macOS exact checkout bun run check 通过;manager/snapshot/result-artifact 专项 47 passed、14 个 Linux-only skipped;独立分页复现得到文本 next 38,结构化 absolute nextByteOffset=45。GitHub Node 22/24 仍各失败 2 项,因此必需 gate 未通过。建议先缩小并闭合现有边界,不要继续扩充存储框架。

const RESULT_CACHE_RECOVERY_NAME =
/^\.retention-lock\.recovery\.[0-9a-f-]{36}$/iu;
const RESULT_ARTIFACT_TEMP_NAME = /^\.[a-f0-9]{64}\.[0-9a-f-]{36}\.tmp$/u;
const SUPPORTED_DESCRIPTOR_PLATFORMS = new Set(["linux"]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 这不是修复 Darwin cache,而是把 exact-result artifact 限定为 Linux。macOS 上 persist 会抛出 unsupported,read 会变成 miss;manager 又只保留 1 MiB finalText,所以更大的 settled result 会被 subagent_result 明确判为 exact unavailable。Issue #180 要求 exact result artifacts 保持可恢复,PR body 也未披露 Linux-only。最小处理可以明确收窄 PR/Issue 范围并保留 #180 未完成;若仍声称 Fixes #180,则需要经验证的 Pi-native/平台安全恢复机制。

return true;
} catch {
// Never expose a projection from an older lifecycle state after a
// failed rebuild; readers fall back to the canonical snapshot.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 投影重建失败时清空所有 bounded projection,而 view.list/get 随后用 entry.projection ?? entry.snapshot 回退到完整 canonical snapshot。资源上限自身一旦失败,模型/UI 就收到未受 aggregate budget 限制的数据,属于 fail-open。请保留最后一个安全 projection 或返回明确的有界失败状态,不能把 canonical full snapshot 当普通 reader fallback。

// Artifact persistence is optional recovery work. Schedule it only
// after settlement has notified waiters and hooks, so a slow or broken
// writer can never hold the lifecycle path or a concurrency slot.
setTimeout(() => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 这个裸 setTimeout 不属于 manager Scope;disposeAll() 不取消也不等待它,所以 Session 关闭后仍可能写 cache、修改 entry 并触发 notify。它也保证 onSettled/delivery clone 发生在 artifact 关联之前。当前“persisted before settlement”测试在 onSettled 里抛出的断言会被生产代码 catch 掉,因此是假阳性。请跟踪并在 teardown 取消/有界等待该任务,同时用不会被生产 catch 吞掉的外部观测验证顺序。

);
}

const noticeFor = (next: number) => `\n[page truncated; next ${next}]`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] line-offset 页面截断时,这个 notice 使用 page-local end,但返回的 nextByteOffset 是加上 pageStartByte 的绝对 cursor。复现 prefix\n + 长行、offset:1,maxBytes:64:正文提示 next 38,details 返回 nextByteOffset:45。Pi 提供给模型的可靠正文不能指向另一个位置;请让提示使用 absolute cursor,或删掉正文中的数值并只暴露一个可消费的权威 cursor。

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:subagentsSubagent delegation, skills, or tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

subagents: 聚合 snapshot 缺少统一 byte 上限

3 participants

@smf-h@tt-a1i@somewan820
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(subagents): enforce UTF-8 snapshot budgets - #238

Open
smf-h wants to merge 4 commits into
openpi-dev:mainfrom
smf-h:fix/subagents-snapshot-180
Open

fix(subagents): enforce UTF-8 snapshot budgets#238
smf-h wants to merge 4 commits into
openpi-dev:mainfrom
smf-h:fix/subagents-snapshot-180

Conversation

@smf-h

Copy link
Copy Markdown
Contributor

概要

  • 为 subagent snapshot 增加严格的 UTF-8 字节级预算。
  • 对多个 agent 进行公平裁剪,并保留生命周期身份、终态状态、错误信息和恢复所需引用。
  • 在展示投影前持久化精确 terminal result,读取时优先使用 artifact 内容。
  • 保留 raw active tool map,确保 ToolUpdate / ToolEnd 生命周期事件仍可关联。
  • 对 malformed、oversized 和非法 snapshot 输入 fail safely。
  • 增加 UTF-8 边界、公平裁剪、幂等投影、artifact precedence 和 tool lifecycle 回归测试。

验证

  • bun test tests/extensions/subagents/snapshot.test.ts tests/extensions/subagents/manager.test.ts tests/extensions/subagents/index.test.ts
  • bun run lint
  • bun run typecheck

Fixes#180

@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from 535806d to 3eb2568CompareAugust 28, 2026 18:07

@somewan820somewan820 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P1 Must-Fix:extensions/subagents/src/manager.ts:427-435 的 persistExactResult() 在 settle() 中执行,未处理 writer 错误;生产 persistResultArtifact() 可因权限、symlink、磁盘满或 collision 抛错,导致 settle() 在 notify()/onSettled() 前失败,waiter/result delivery 可能永远收不到终态。请让 artifact cache fail-soft,或保证 settlement 先终态通知再处理缓存失败,并补充 failure-path tests。

P1 Must-Fix:exact-result artifact 没有 retention/aggregate size limit,长期运行会持续积累唯一结果并耗尽 cache volume。请加入 bounded cleanup/retention 及写入/清理失败恢复。

P2 Should-Fix:enforceSnapshotBudget() 将有损 projection 写回 live entry.snapshot,transcript omits 后 takeover UI 无 rehydrate 路径,历史会静默消失。执行 snapshot 与 UI/model projection 应分离,或提供显式 rehydrate/omission 状态。P3:snapshot parsers 当前无生产调用方。静态审查,未运行测试。

@github-actionsgithub-actionsBot added the area:subagents Subagent delegation, skills, or tests label Aug 30, 2026
@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from 3a0cc46 to 51b6a22CompareAugust 30, 2026 08:34
@smf-h
smf-h requested a review from somewan820August 30, 2026 08:37
@smf-h

smf-h commented Aug 31, 2026

Copy link
Copy Markdown
ContributorAuthor

感谢审查,已在最新提交 中处理这些问题。

P1:artifact persistence 不再阻断 settlement

 settle() 现在先完成 terminal status、retained result、projection 更新、 notify() 、 onSettled() 、waiter 唤醒及并发槽释放,再将 artifact persistence 调度为异步的可选恢复工作。因此权限错误、symlink、磁盘满、collision、lock contention 或 writer throw 都不会阻止终态通知,也不会让 waiter/result delivery 永久等待。

延迟 writer 会捕获 cache/filesystem 异常,严格验证 ref 只能是 { version: 1, digest } ,验证 digest 与 exact result 内容匹配,并使用 run generation guard 防止旧 run 的延迟 writer 把 artifact 附加到重启后的新 run。失败时不会回滚 terminal status 或 finalText 。

新增/更新的 manager failure‑path tests 验证了 writer throw、wrong digest 和非法 ref 场景下: waitFor() 返回、终态可从 per‑id listener 观察、 onSettled() 被调用,且后续仍可 spawn。

P1:artifact cache bounded retention

cache 现在有硬性 retention 上限:最多 64 个 artifact 文件、最多 64 MiB payload;自定义 maxFiles / maxBytes 不能超过 hard cap。retention eviction 和 stale metadata cleanup 在 cache lock 内完成。超大写入在触碰 cache 前拒绝,不留下 partial artifact 或 .tmp ,后续正常写入仍可继续。

锁获取使用有界重试;ownership 不确定、PID reuse、lock contention、symlink、unknown/recovery entry 或平台无法提供安全 no‑follow 操作时均 fail‑closed。release cleanup 失败也不会把已成功发布的 ref 变成失败。

P2:canonical snapshot 与 projection 分离

 entry.snapshot 保持 canonical event‑folding state, entry.projection 是独立的 aggregate‑bounded read model;projection 不再写回 canonical snapshot。takeover 继续使用 view.getFull?.(id) ?? view.get(id) 。

projection rebuild 失败时会清除旧 projection,读取方回退到最新 canonical state,避免 settled 条目继续显示旧的 running 。exact‑result recovery 只使用经过校验的 artifact 或明确完整的 canonical retained finalText ,不会把 projection 当作 recovery source。新增 finalTextTruncated 标记:当 artifact 不可用且 retained text 被 1 MiB cap 截断时, subagent_result 会明确报告 exact result unavailable,而不是返回截断前缀。

P3:snapshot projection 已有生产调用方

manager 会在 entry 注册和 lifecycle 更新时调用 snapshot projection;canonical snapshot 仍供 event folding 和 takeover rehydration 使用,model‑facing 工具使用 bounded projection。

验证

已执行:

text

node --test --experimental-strip-types tests/extensions/subagents/manager.test.ts tests/extensions/subagents/result-artifact.test.ts tests/extensions/subagents/snapshot.test.ts tests/extensions/subagents/takeover.test.ts tests/extensions/subagents/index.test.ts

结果:88 tests,74 passed,0 failed,14 skipped。

另已执行并通过:

text

bun run typecheck
bun run lint

14 个 skipped 测试依赖 POSIX descriptor‑relative/no‑follow 文件系统能力;当前环境为 Windows,因此按预期跳过。Windows fail‑closed 测试已通过。POSIX lock recovery、并发 retention 和 symlink cache 行为未能在当前 Windows 环境实际执行,未修改 skip 条件或削弱安全行为。

@tt-a1itt-a1i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

复审 exact head b3d7d4665f160e0e3eb8fcaba73c27f536025885。目标是用UTF-8字节预算限制子代理展示快照,同时通过canonical state/受保护artifact保留恢复能力。旧review中的settlement被缓存异常阻塞、缓存无上限、展示投影覆盖canonical state三项都有实质改进,不重复计入。

Standards

独立人工审查无额外架构要求;现有自动守卫确认1项:新增subagent_result没有完成child分类,也未纳入现有tool surface,违反已有工具归属契约。请在现有表中补齐,不要新增一层权限系统。

Spec

3项已复现P2:

  1. Darwin使用/dev/fd/目录句柄拼子路径,macOS上的cache读写失败。
  2. 超过16KiB的单行结果无法通过line offset/limit恢复;limit=1仍只有截断提示。
  3. truncateUtf8Head把四字节字符的3字节前缀解码为替换字符,却误判为完整UTF-8前缀。

详见行内。它们直接影响本PR承诺的可恢复性、兼容性和UTF-8正确性;无需扩展成新存储框架。

验证

Node24/macOS exact checkout:check因index.ts/manager.ts格式失败;完整test失败,明确包含child drift guard和Mac artifact错误。独立重跑guard仍报subagent_result未分类;Mac artifact专项仍报ENOENT /dev/fd/.../cache。独立pageResultText复现20KiB单行只返回60-byte提示;truncateUtf8Head("😀",3)返回替换字符,4096字节真实预算同样复现。GitHub Node22/24 CI也为失败。不把其他并行运行出现的setup超时直接归因本PR;未做实际Pi UI或发布验收。

请修复这些边界并跑通check/test后再复审。本轮未修改源码、提交或合并。

});

pi.registerTool({
name: "subagent_result",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 将新工具纳入现有归属和 child 权限分类

新增 subagent_result 后,extensions/shared/child-session.ts 的 child-safe/excluded 列表都没有它,OPENPI_TOOL_SURFACE.subagents 也未登记。当前现有 every registered package tool is classified ... 测试稳定失败,明确报该工具未分类。请按真实用途加入已有 parent-only/child-safe 分类及 subagents 工具可见性表,验证关闭能力和子会话边界,不应让新工具绕开已建立的登记契约。

function descriptorRelativePath(fd: number, fallback: string) {
if (process.platform === "linux") return `/proc/self/fd/${fd}`;
if (
process.platform === "darwin" ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Darwin 的 /dev/fd 不能当作 Linux 的目录遍历入口

本机 macOS 实测目录fd本身可fstat,/dev/fd/<fd>也显示directory,但访问/dev/fd/<fd>/package.json即ENOENT;它不像Linux /proc/self/fd/<fd>支持继续拼接子路径。因此这里声明支持darwin后,openCheckedDirectory中的mkdir/read始终失败,artifact专项稳定报ENOENT mkdir /dev/fd/14/cache,不是测试并行噪声。请验证Mac可用、具有同等安全边界的实现;如果决定暂不支持,需在I/O前明确fail-closed并披露精确恢复的退化,不能仅跳过测试或声称兼容性未变。未推断其他BSD行为。

truncated: false,
};
}
const suffix = `\n[page truncated at ${maxBytes} bytes; reduce the requested range]`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 为超长单行保留可继续读取的恢复路径

当前工具只有行offset/limit,超出字节预算后使用的truncateHead不会截取首条超长行的部分内容。真实输入20KiB单行+尾标记,offset0/limit200与offset0/limit1都只返回60字节的截断提示;offset1已经EOF。用户按提示reduce range也无法取得正文,违反exact recovery用途。请提供最小行内继续读取方式或等价受保护恢复路径,同时保持页面字节上限,并补单行超限的完整恢复测试。

Comment threadextensions/subagents/src/snapshot.ts Outdated
let end = maxBytes;
while (end > 0) {
const candidate = bytes.subarray(0, end).toString("utf8");
if (byteLength(candidate) === end) return candidate;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 解码后字节数相同不代表 UTF-8 边界完整

truncateUtf8Head("😀", 3)实际返回U+FFFD替换字符:四字节emoji的前三字节解码成一个三字节replacement,恰好满足byteLength(candidate) === end,因此提前返回了并非原文前缀的字符串。真实4096预算下,4093个ASCII字符后接emoji也会出现同样损坏。现测试只有三字节中文,漏掉了此情形。请按codepoint边界回退或采用严格解码验证,并覆盖四字节字符各切割位置;不要让有界裁剪改写原文内容。

@tt-a1i

Copy link
Copy Markdown
Collaborator

当前 head b3d7d46 已复审,Changes Requested。旧的缓存失败阻塞settlement、无缓存上限、canonical/projection混用已有实质修复;新确认4项P2:工具分类遗漏、Mac缓存路径不可用、超长单行无法恢复、四字节UTF-8截断产生替换字符。check/test及必需CI当前均非绿色,详见 #238 (review) 。建议在现有边界内逐项修,不增加新框架。本轮未改源码或合并。

Keep canonical settlement separate from bounded projection, persist artifacts fail-soft, and recover through subagent_result(id, offset, limit) instead of cache pathnames.
Keep exact-result paging, UTF-8 truncation, Darwin cache paths, and
child-tool classification within the existing subagent surface.
@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from ba54ec5 to 781775eCompareSeptember 1, 2026 04:34

@tt-a1itt-a1i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

复审 exact head 781775e9acf419d34bff904c1e1353229251202e。目标是给 Direct Subagent snapshot 建立聚合 UTF-8 硬上限,同时保留 exact result 的恢复能力;价值是避免长 Session 的模型/UI read model 持续膨胀。当前提交已经修好工具分类、四字节 UTF-8 截断和超长单行分页,但仍有跨平台恢复、fail-open 和生命周期问题。

Standards

2 项:projection rebuild 失败会回退到完整 canonical snapshot,硬资源上限 fail-open;artifact persistence 用未受 Session Scope 管理的裸 setTimeout,shutdown 不取消/等待,且测试把 onSettled 内断言异常吞掉了。

Spec

2 项:exact artifact 现在仅支持 Linux,macOS 上超过 1 MiB 的终态结果不可恢复;Issue #180 要求的旧版/超大 persisted snapshot restore 边界仍没有实现,当前 projection 只存在于进程内 manager。另有 line-offset 转 byte cursor 时文本提示的 cursor 不是绝对值。

验证:macOS exact checkout bun run check 通过;manager/snapshot/result-artifact 专项 47 passed、14 个 Linux-only skipped;独立分页复现得到文本 next 38,结构化 absolute nextByteOffset=45。GitHub Node 22/24 仍各失败 2 项,因此必需 gate 未通过。建议先缩小并闭合现有边界,不要继续扩充存储框架。

const RESULT_CACHE_RECOVERY_NAME =
/^\.retention-lock\.recovery\.[0-9a-f-]{36}$/iu;
const RESULT_ARTIFACT_TEMP_NAME = /^\.[a-f0-9]{64}\.[0-9a-f-]{36}\.tmp$/u;
const SUPPORTED_DESCRIPTOR_PLATFORMS = new Set(["linux"]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 这不是修复 Darwin cache,而是把 exact-result artifact 限定为 Linux。macOS 上 persist 会抛出 unsupported,read 会变成 miss;manager 又只保留 1 MiB finalText,所以更大的 settled result 会被 subagent_result 明确判为 exact unavailable。Issue #180 要求 exact result artifacts 保持可恢复,PR body 也未披露 Linux-only。最小处理可以明确收窄 PR/Issue 范围并保留 #180 未完成;若仍声称 Fixes #180,则需要经验证的 Pi-native/平台安全恢复机制。

return true;
} catch {
// Never expose a projection from an older lifecycle state after a
// failed rebuild; readers fall back to the canonical snapshot.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 投影重建失败时清空所有 bounded projection,而 view.list/get 随后用 entry.projection ?? entry.snapshot 回退到完整 canonical snapshot。资源上限自身一旦失败,模型/UI 就收到未受 aggregate budget 限制的数据,属于 fail-open。请保留最后一个安全 projection 或返回明确的有界失败状态,不能把 canonical full snapshot 当普通 reader fallback。

// Artifact persistence is optional recovery work. Schedule it only
// after settlement has notified waiters and hooks, so a slow or broken
// writer can never hold the lifecycle path or a concurrency slot.
setTimeout(() => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 这个裸 setTimeout 不属于 manager Scope;disposeAll() 不取消也不等待它,所以 Session 关闭后仍可能写 cache、修改 entry 并触发 notify。它也保证 onSettled/delivery clone 发生在 artifact 关联之前。当前“persisted before settlement”测试在 onSettled 里抛出的断言会被生产代码 catch 掉,因此是假阳性。请跟踪并在 teardown 取消/有界等待该任务,同时用不会被生产 catch 吞掉的外部观测验证顺序。

);
}

const noticeFor = (next: number) => `\n[page truncated; next ${next}]`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] line-offset 页面截断时,这个 notice 使用 page-local end,但返回的 nextByteOffset 是加上 pageStartByte 的绝对 cursor。复现 prefix\n + 长行、offset:1,maxBytes:64:正文提示 next 38,details 返回 nextByteOffset:45。Pi 提供给模型的可靠正文不能指向另一个位置;请让提示使用 absolute cursor,或删掉正文中的数值并只暴露一个可消费的权威 cursor。

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:subagentsSubagent delegation, skills, or tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

subagents: 聚合 snapshot 缺少统一 byte 上限

3 participants

@smf-h@tt-a1i@somewan820
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(subagents): enforce UTF-8 snapshot budgets - #238

Open
smf-h wants to merge 4 commits into
openpi-dev:mainfrom
smf-h:fix/subagents-snapshot-180
Open

fix(subagents): enforce UTF-8 snapshot budgets#238
smf-h wants to merge 4 commits into
openpi-dev:mainfrom
smf-h:fix/subagents-snapshot-180

Conversation

@smf-h

Copy link
Copy Markdown
Contributor

概要

  • 为 subagent snapshot 增加严格的 UTF-8 字节级预算。
  • 对多个 agent 进行公平裁剪,并保留生命周期身份、终态状态、错误信息和恢复所需引用。
  • 在展示投影前持久化精确 terminal result,读取时优先使用 artifact 内容。
  • 保留 raw active tool map,确保 ToolUpdate / ToolEnd 生命周期事件仍可关联。
  • 对 malformed、oversized 和非法 snapshot 输入 fail safely。
  • 增加 UTF-8 边界、公平裁剪、幂等投影、artifact precedence 和 tool lifecycle 回归测试。

验证

  • bun test tests/extensions/subagents/snapshot.test.ts tests/extensions/subagents/manager.test.ts tests/extensions/subagents/index.test.ts
  • bun run lint
  • bun run typecheck

Fixes#180

@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from 535806d to 3eb2568CompareAugust 28, 2026 18:07

@somewan820somewan820 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P1 Must-Fix:extensions/subagents/src/manager.ts:427-435 的 persistExactResult() 在 settle() 中执行,未处理 writer 错误;生产 persistResultArtifact() 可因权限、symlink、磁盘满或 collision 抛错,导致 settle() 在 notify()/onSettled() 前失败,waiter/result delivery 可能永远收不到终态。请让 artifact cache fail-soft,或保证 settlement 先终态通知再处理缓存失败,并补充 failure-path tests。

P1 Must-Fix:exact-result artifact 没有 retention/aggregate size limit,长期运行会持续积累唯一结果并耗尽 cache volume。请加入 bounded cleanup/retention 及写入/清理失败恢复。

P2 Should-Fix:enforceSnapshotBudget() 将有损 projection 写回 live entry.snapshot,transcript omits 后 takeover UI 无 rehydrate 路径,历史会静默消失。执行 snapshot 与 UI/model projection 应分离,或提供显式 rehydrate/omission 状态。P3:snapshot parsers 当前无生产调用方。静态审查,未运行测试。

@github-actionsgithub-actionsBot added the area:subagents Subagent delegation, skills, or tests label Aug 30, 2026
@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from 3a0cc46 to 51b6a22CompareAugust 30, 2026 08:34
@smf-h
smf-h requested a review from somewan820August 30, 2026 08:37
@smf-h

smf-h commented Aug 31, 2026

Copy link
Copy Markdown
ContributorAuthor

感谢审查,已在最新提交 中处理这些问题。

P1:artifact persistence 不再阻断 settlement

 settle() 现在先完成 terminal status、retained result、projection 更新、 notify() 、 onSettled() 、waiter 唤醒及并发槽释放,再将 artifact persistence 调度为异步的可选恢复工作。因此权限错误、symlink、磁盘满、collision、lock contention 或 writer throw 都不会阻止终态通知,也不会让 waiter/result delivery 永久等待。

延迟 writer 会捕获 cache/filesystem 异常,严格验证 ref 只能是 { version: 1, digest } ,验证 digest 与 exact result 内容匹配,并使用 run generation guard 防止旧 run 的延迟 writer 把 artifact 附加到重启后的新 run。失败时不会回滚 terminal status 或 finalText 。

新增/更新的 manager failure‑path tests 验证了 writer throw、wrong digest 和非法 ref 场景下: waitFor() 返回、终态可从 per‑id listener 观察、 onSettled() 被调用,且后续仍可 spawn。

P1:artifact cache bounded retention

cache 现在有硬性 retention 上限:最多 64 个 artifact 文件、最多 64 MiB payload;自定义 maxFiles / maxBytes 不能超过 hard cap。retention eviction 和 stale metadata cleanup 在 cache lock 内完成。超大写入在触碰 cache 前拒绝,不留下 partial artifact 或 .tmp ,后续正常写入仍可继续。

锁获取使用有界重试;ownership 不确定、PID reuse、lock contention、symlink、unknown/recovery entry 或平台无法提供安全 no‑follow 操作时均 fail‑closed。release cleanup 失败也不会把已成功发布的 ref 变成失败。

P2:canonical snapshot 与 projection 分离

 entry.snapshot 保持 canonical event‑folding state, entry.projection 是独立的 aggregate‑bounded read model;projection 不再写回 canonical snapshot。takeover 继续使用 view.getFull?.(id) ?? view.get(id) 。

projection rebuild 失败时会清除旧 projection,读取方回退到最新 canonical state,避免 settled 条目继续显示旧的 running 。exact‑result recovery 只使用经过校验的 artifact 或明确完整的 canonical retained finalText ,不会把 projection 当作 recovery source。新增 finalTextTruncated 标记:当 artifact 不可用且 retained text 被 1 MiB cap 截断时, subagent_result 会明确报告 exact result unavailable,而不是返回截断前缀。

P3:snapshot projection 已有生产调用方

manager 会在 entry 注册和 lifecycle 更新时调用 snapshot projection;canonical snapshot 仍供 event folding 和 takeover rehydration 使用,model‑facing 工具使用 bounded projection。

验证

已执行:

text

node --test --experimental-strip-types tests/extensions/subagents/manager.test.ts tests/extensions/subagents/result-artifact.test.ts tests/extensions/subagents/snapshot.test.ts tests/extensions/subagents/takeover.test.ts tests/extensions/subagents/index.test.ts

结果:88 tests,74 passed,0 failed,14 skipped。

另已执行并通过:

text

bun run typecheck
bun run lint

14 个 skipped 测试依赖 POSIX descriptor‑relative/no‑follow 文件系统能力;当前环境为 Windows,因此按预期跳过。Windows fail‑closed 测试已通过。POSIX lock recovery、并发 retention 和 symlink cache 行为未能在当前 Windows 环境实际执行,未修改 skip 条件或削弱安全行为。

@tt-a1itt-a1i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

复审 exact head b3d7d4665f160e0e3eb8fcaba73c27f536025885。目标是用UTF-8字节预算限制子代理展示快照,同时通过canonical state/受保护artifact保留恢复能力。旧review中的settlement被缓存异常阻塞、缓存无上限、展示投影覆盖canonical state三项都有实质改进,不重复计入。

Standards

独立人工审查无额外架构要求;现有自动守卫确认1项:新增subagent_result没有完成child分类,也未纳入现有tool surface,违反已有工具归属契约。请在现有表中补齐,不要新增一层权限系统。

Spec

3项已复现P2:

  1. Darwin使用/dev/fd/目录句柄拼子路径,macOS上的cache读写失败。
  2. 超过16KiB的单行结果无法通过line offset/limit恢复;limit=1仍只有截断提示。
  3. truncateUtf8Head把四字节字符的3字节前缀解码为替换字符,却误判为完整UTF-8前缀。

详见行内。它们直接影响本PR承诺的可恢复性、兼容性和UTF-8正确性;无需扩展成新存储框架。

验证

Node24/macOS exact checkout:check因index.ts/manager.ts格式失败;完整test失败,明确包含child drift guard和Mac artifact错误。独立重跑guard仍报subagent_result未分类;Mac artifact专项仍报ENOENT /dev/fd/.../cache。独立pageResultText复现20KiB单行只返回60-byte提示;truncateUtf8Head("😀",3)返回替换字符,4096字节真实预算同样复现。GitHub Node22/24 CI也为失败。不把其他并行运行出现的setup超时直接归因本PR;未做实际Pi UI或发布验收。

请修复这些边界并跑通check/test后再复审。本轮未修改源码、提交或合并。

});

pi.registerTool({
name: "subagent_result",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 将新工具纳入现有归属和 child 权限分类

新增 subagent_result 后,extensions/shared/child-session.ts 的 child-safe/excluded 列表都没有它,OPENPI_TOOL_SURFACE.subagents 也未登记。当前现有 every registered package tool is classified ... 测试稳定失败,明确报该工具未分类。请按真实用途加入已有 parent-only/child-safe 分类及 subagents 工具可见性表,验证关闭能力和子会话边界,不应让新工具绕开已建立的登记契约。

function descriptorRelativePath(fd: number, fallback: string) {
if (process.platform === "linux") return `/proc/self/fd/${fd}`;
if (
process.platform === "darwin" ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Darwin 的 /dev/fd 不能当作 Linux 的目录遍历入口

本机 macOS 实测目录fd本身可fstat,/dev/fd/<fd>也显示directory,但访问/dev/fd/<fd>/package.json即ENOENT;它不像Linux /proc/self/fd/<fd>支持继续拼接子路径。因此这里声明支持darwin后,openCheckedDirectory中的mkdir/read始终失败,artifact专项稳定报ENOENT mkdir /dev/fd/14/cache,不是测试并行噪声。请验证Mac可用、具有同等安全边界的实现;如果决定暂不支持,需在I/O前明确fail-closed并披露精确恢复的退化,不能仅跳过测试或声称兼容性未变。未推断其他BSD行为。

truncated: false,
};
}
const suffix = `\n[page truncated at ${maxBytes} bytes; reduce the requested range]`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 为超长单行保留可继续读取的恢复路径

当前工具只有行offset/limit,超出字节预算后使用的truncateHead不会截取首条超长行的部分内容。真实输入20KiB单行+尾标记,offset0/limit200与offset0/limit1都只返回60字节的截断提示;offset1已经EOF。用户按提示reduce range也无法取得正文,违反exact recovery用途。请提供最小行内继续读取方式或等价受保护恢复路径,同时保持页面字节上限,并补单行超限的完整恢复测试。

Comment threadextensions/subagents/src/snapshot.ts Outdated
let end = maxBytes;
while (end > 0) {
const candidate = bytes.subarray(0, end).toString("utf8");
if (byteLength(candidate) === end) return candidate;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 解码后字节数相同不代表 UTF-8 边界完整

truncateUtf8Head("😀", 3)实际返回U+FFFD替换字符:四字节emoji的前三字节解码成一个三字节replacement,恰好满足byteLength(candidate) === end,因此提前返回了并非原文前缀的字符串。真实4096预算下,4093个ASCII字符后接emoji也会出现同样损坏。现测试只有三字节中文,漏掉了此情形。请按codepoint边界回退或采用严格解码验证,并覆盖四字节字符各切割位置;不要让有界裁剪改写原文内容。

@tt-a1i

Copy link
Copy Markdown
Collaborator

当前 head b3d7d46 已复审,Changes Requested。旧的缓存失败阻塞settlement、无缓存上限、canonical/projection混用已有实质修复;新确认4项P2:工具分类遗漏、Mac缓存路径不可用、超长单行无法恢复、四字节UTF-8截断产生替换字符。check/test及必需CI当前均非绿色,详见 #238 (review) 。建议在现有边界内逐项修,不增加新框架。本轮未改源码或合并。

Keep canonical settlement separate from bounded projection, persist artifacts fail-soft, and recover through subagent_result(id, offset, limit) instead of cache pathnames.
Keep exact-result paging, UTF-8 truncation, Darwin cache paths, and
child-tool classification within the existing subagent surface.
@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from ba54ec5 to 781775eCompareSeptember 1, 2026 04:34

@tt-a1itt-a1i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

复审 exact head 781775e9acf419d34bff904c1e1353229251202e。目标是给 Direct Subagent snapshot 建立聚合 UTF-8 硬上限,同时保留 exact result 的恢复能力;价值是避免长 Session 的模型/UI read model 持续膨胀。当前提交已经修好工具分类、四字节 UTF-8 截断和超长单行分页,但仍有跨平台恢复、fail-open 和生命周期问题。

Standards

2 项:projection rebuild 失败会回退到完整 canonical snapshot,硬资源上限 fail-open;artifact persistence 用未受 Session Scope 管理的裸 setTimeout,shutdown 不取消/等待,且测试把 onSettled 内断言异常吞掉了。

Spec

2 项:exact artifact 现在仅支持 Linux,macOS 上超过 1 MiB 的终态结果不可恢复;Issue #180 要求的旧版/超大 persisted snapshot restore 边界仍没有实现,当前 projection 只存在于进程内 manager。另有 line-offset 转 byte cursor 时文本提示的 cursor 不是绝对值。

验证:macOS exact checkout bun run check 通过;manager/snapshot/result-artifact 专项 47 passed、14 个 Linux-only skipped;独立分页复现得到文本 next 38,结构化 absolute nextByteOffset=45。GitHub Node 22/24 仍各失败 2 项,因此必需 gate 未通过。建议先缩小并闭合现有边界,不要继续扩充存储框架。

const RESULT_CACHE_RECOVERY_NAME =
/^\.retention-lock\.recovery\.[0-9a-f-]{36}$/iu;
const RESULT_ARTIFACT_TEMP_NAME = /^\.[a-f0-9]{64}\.[0-9a-f-]{36}\.tmp$/u;
const SUPPORTED_DESCRIPTOR_PLATFORMS = new Set(["linux"]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 这不是修复 Darwin cache,而是把 exact-result artifact 限定为 Linux。macOS 上 persist 会抛出 unsupported,read 会变成 miss;manager 又只保留 1 MiB finalText,所以更大的 settled result 会被 subagent_result 明确判为 exact unavailable。Issue #180 要求 exact result artifacts 保持可恢复,PR body 也未披露 Linux-only。最小处理可以明确收窄 PR/Issue 范围并保留 #180 未完成;若仍声称 Fixes #180,则需要经验证的 Pi-native/平台安全恢复机制。

return true;
} catch {
// Never expose a projection from an older lifecycle state after a
// failed rebuild; readers fall back to the canonical snapshot.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 投影重建失败时清空所有 bounded projection,而 view.list/get 随后用 entry.projection ?? entry.snapshot 回退到完整 canonical snapshot。资源上限自身一旦失败,模型/UI 就收到未受 aggregate budget 限制的数据,属于 fail-open。请保留最后一个安全 projection 或返回明确的有界失败状态,不能把 canonical full snapshot 当普通 reader fallback。

// Artifact persistence is optional recovery work. Schedule it only
// after settlement has notified waiters and hooks, so a slow or broken
// writer can never hold the lifecycle path or a concurrency slot.
setTimeout(() => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 这个裸 setTimeout 不属于 manager Scope;disposeAll() 不取消也不等待它,所以 Session 关闭后仍可能写 cache、修改 entry 并触发 notify。它也保证 onSettled/delivery clone 发生在 artifact 关联之前。当前“persisted before settlement”测试在 onSettled 里抛出的断言会被生产代码 catch 掉,因此是假阳性。请跟踪并在 teardown 取消/有界等待该任务,同时用不会被生产 catch 吞掉的外部观测验证顺序。

);
}

const noticeFor = (next: number) => `\n[page truncated; next ${next}]`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] line-offset 页面截断时,这个 notice 使用 page-local end,但返回的 nextByteOffset 是加上 pageStartByte 的绝对 cursor。复现 prefix\n + 长行、offset:1,maxBytes:64:正文提示 next 38,details 返回 nextByteOffset:45。Pi 提供给模型的可靠正文不能指向另一个位置;请让提示使用 absolute cursor,或删掉正文中的数值并只暴露一个可消费的权威 cursor。

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:subagentsSubagent delegation, skills, or tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

subagents: 聚合 snapshot 缺少统一 byte 上限

3 participants

@smf-h@tt-a1i@somewan820
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(subagents): enforce UTF-8 snapshot budgets - #238

Open
smf-h wants to merge 4 commits into
openpi-dev:mainfrom
smf-h:fix/subagents-snapshot-180
Open

fix(subagents): enforce UTF-8 snapshot budgets#238
smf-h wants to merge 4 commits into
openpi-dev:mainfrom
smf-h:fix/subagents-snapshot-180

Conversation

@smf-h

Copy link
Copy Markdown
Contributor

概要

  • 为 subagent snapshot 增加严格的 UTF-8 字节级预算。
  • 对多个 agent 进行公平裁剪,并保留生命周期身份、终态状态、错误信息和恢复所需引用。
  • 在展示投影前持久化精确 terminal result,读取时优先使用 artifact 内容。
  • 保留 raw active tool map,确保 ToolUpdate / ToolEnd 生命周期事件仍可关联。
  • 对 malformed、oversized 和非法 snapshot 输入 fail safely。
  • 增加 UTF-8 边界、公平裁剪、幂等投影、artifact precedence 和 tool lifecycle 回归测试。

验证

  • bun test tests/extensions/subagents/snapshot.test.ts tests/extensions/subagents/manager.test.ts tests/extensions/subagents/index.test.ts
  • bun run lint
  • bun run typecheck

Fixes#180

@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from 535806d to 3eb2568CompareAugust 28, 2026 18:07

@somewan820somewan820 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P1 Must-Fix:extensions/subagents/src/manager.ts:427-435 的 persistExactResult() 在 settle() 中执行,未处理 writer 错误;生产 persistResultArtifact() 可因权限、symlink、磁盘满或 collision 抛错,导致 settle() 在 notify()/onSettled() 前失败,waiter/result delivery 可能永远收不到终态。请让 artifact cache fail-soft,或保证 settlement 先终态通知再处理缓存失败,并补充 failure-path tests。

P1 Must-Fix:exact-result artifact 没有 retention/aggregate size limit,长期运行会持续积累唯一结果并耗尽 cache volume。请加入 bounded cleanup/retention 及写入/清理失败恢复。

P2 Should-Fix:enforceSnapshotBudget() 将有损 projection 写回 live entry.snapshot,transcript omits 后 takeover UI 无 rehydrate 路径,历史会静默消失。执行 snapshot 与 UI/model projection 应分离,或提供显式 rehydrate/omission 状态。P3:snapshot parsers 当前无生产调用方。静态审查,未运行测试。

@github-actionsgithub-actionsBot added the area:subagents Subagent delegation, skills, or tests label Aug 30, 2026
@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from 3a0cc46 to 51b6a22CompareAugust 30, 2026 08:34
@smf-h
smf-h requested a review from somewan820August 30, 2026 08:37
@smf-h

smf-h commented Aug 31, 2026

Copy link
Copy Markdown
ContributorAuthor

感谢审查,已在最新提交 中处理这些问题。

P1:artifact persistence 不再阻断 settlement

 settle() 现在先完成 terminal status、retained result、projection 更新、 notify() 、 onSettled() 、waiter 唤醒及并发槽释放,再将 artifact persistence 调度为异步的可选恢复工作。因此权限错误、symlink、磁盘满、collision、lock contention 或 writer throw 都不会阻止终态通知,也不会让 waiter/result delivery 永久等待。

延迟 writer 会捕获 cache/filesystem 异常,严格验证 ref 只能是 { version: 1, digest } ,验证 digest 与 exact result 内容匹配,并使用 run generation guard 防止旧 run 的延迟 writer 把 artifact 附加到重启后的新 run。失败时不会回滚 terminal status 或 finalText 。

新增/更新的 manager failure‑path tests 验证了 writer throw、wrong digest 和非法 ref 场景下: waitFor() 返回、终态可从 per‑id listener 观察、 onSettled() 被调用,且后续仍可 spawn。

P1:artifact cache bounded retention

cache 现在有硬性 retention 上限:最多 64 个 artifact 文件、最多 64 MiB payload;自定义 maxFiles / maxBytes 不能超过 hard cap。retention eviction 和 stale metadata cleanup 在 cache lock 内完成。超大写入在触碰 cache 前拒绝,不留下 partial artifact 或 .tmp ,后续正常写入仍可继续。

锁获取使用有界重试;ownership 不确定、PID reuse、lock contention、symlink、unknown/recovery entry 或平台无法提供安全 no‑follow 操作时均 fail‑closed。release cleanup 失败也不会把已成功发布的 ref 变成失败。

P2:canonical snapshot 与 projection 分离

 entry.snapshot 保持 canonical event‑folding state, entry.projection 是独立的 aggregate‑bounded read model;projection 不再写回 canonical snapshot。takeover 继续使用 view.getFull?.(id) ?? view.get(id) 。

projection rebuild 失败时会清除旧 projection,读取方回退到最新 canonical state,避免 settled 条目继续显示旧的 running 。exact‑result recovery 只使用经过校验的 artifact 或明确完整的 canonical retained finalText ,不会把 projection 当作 recovery source。新增 finalTextTruncated 标记:当 artifact 不可用且 retained text 被 1 MiB cap 截断时, subagent_result 会明确报告 exact result unavailable,而不是返回截断前缀。

P3:snapshot projection 已有生产调用方

manager 会在 entry 注册和 lifecycle 更新时调用 snapshot projection;canonical snapshot 仍供 event folding 和 takeover rehydration 使用,model‑facing 工具使用 bounded projection。

验证

已执行:

text

node --test --experimental-strip-types tests/extensions/subagents/manager.test.ts tests/extensions/subagents/result-artifact.test.ts tests/extensions/subagents/snapshot.test.ts tests/extensions/subagents/takeover.test.ts tests/extensions/subagents/index.test.ts

结果:88 tests,74 passed,0 failed,14 skipped。

另已执行并通过:

text

bun run typecheck
bun run lint

14 个 skipped 测试依赖 POSIX descriptor‑relative/no‑follow 文件系统能力;当前环境为 Windows,因此按预期跳过。Windows fail‑closed 测试已通过。POSIX lock recovery、并发 retention 和 symlink cache 行为未能在当前 Windows 环境实际执行,未修改 skip 条件或削弱安全行为。

@tt-a1itt-a1i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

复审 exact head b3d7d4665f160e0e3eb8fcaba73c27f536025885。目标是用UTF-8字节预算限制子代理展示快照,同时通过canonical state/受保护artifact保留恢复能力。旧review中的settlement被缓存异常阻塞、缓存无上限、展示投影覆盖canonical state三项都有实质改进,不重复计入。

Standards

独立人工审查无额外架构要求;现有自动守卫确认1项:新增subagent_result没有完成child分类,也未纳入现有tool surface,违反已有工具归属契约。请在现有表中补齐,不要新增一层权限系统。

Spec

3项已复现P2:

  1. Darwin使用/dev/fd/目录句柄拼子路径,macOS上的cache读写失败。
  2. 超过16KiB的单行结果无法通过line offset/limit恢复;limit=1仍只有截断提示。
  3. truncateUtf8Head把四字节字符的3字节前缀解码为替换字符,却误判为完整UTF-8前缀。

详见行内。它们直接影响本PR承诺的可恢复性、兼容性和UTF-8正确性;无需扩展成新存储框架。

验证

Node24/macOS exact checkout:check因index.ts/manager.ts格式失败;完整test失败,明确包含child drift guard和Mac artifact错误。独立重跑guard仍报subagent_result未分类;Mac artifact专项仍报ENOENT /dev/fd/.../cache。独立pageResultText复现20KiB单行只返回60-byte提示;truncateUtf8Head("😀",3)返回替换字符,4096字节真实预算同样复现。GitHub Node22/24 CI也为失败。不把其他并行运行出现的setup超时直接归因本PR;未做实际Pi UI或发布验收。

请修复这些边界并跑通check/test后再复审。本轮未修改源码、提交或合并。

});

pi.registerTool({
name: "subagent_result",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 将新工具纳入现有归属和 child 权限分类

新增 subagent_result 后,extensions/shared/child-session.ts 的 child-safe/excluded 列表都没有它,OPENPI_TOOL_SURFACE.subagents 也未登记。当前现有 every registered package tool is classified ... 测试稳定失败,明确报该工具未分类。请按真实用途加入已有 parent-only/child-safe 分类及 subagents 工具可见性表,验证关闭能力和子会话边界,不应让新工具绕开已建立的登记契约。

function descriptorRelativePath(fd: number, fallback: string) {
if (process.platform === "linux") return `/proc/self/fd/${fd}`;
if (
process.platform === "darwin" ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Darwin 的 /dev/fd 不能当作 Linux 的目录遍历入口

本机 macOS 实测目录fd本身可fstat,/dev/fd/<fd>也显示directory,但访问/dev/fd/<fd>/package.json即ENOENT;它不像Linux /proc/self/fd/<fd>支持继续拼接子路径。因此这里声明支持darwin后,openCheckedDirectory中的mkdir/read始终失败,artifact专项稳定报ENOENT mkdir /dev/fd/14/cache,不是测试并行噪声。请验证Mac可用、具有同等安全边界的实现;如果决定暂不支持,需在I/O前明确fail-closed并披露精确恢复的退化,不能仅跳过测试或声称兼容性未变。未推断其他BSD行为。

truncated: false,
};
}
const suffix = `\n[page truncated at ${maxBytes} bytes; reduce the requested range]`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 为超长单行保留可继续读取的恢复路径

当前工具只有行offset/limit,超出字节预算后使用的truncateHead不会截取首条超长行的部分内容。真实输入20KiB单行+尾标记,offset0/limit200与offset0/limit1都只返回60字节的截断提示;offset1已经EOF。用户按提示reduce range也无法取得正文,违反exact recovery用途。请提供最小行内继续读取方式或等价受保护恢复路径,同时保持页面字节上限,并补单行超限的完整恢复测试。

Comment threadextensions/subagents/src/snapshot.ts Outdated
let end = maxBytes;
while (end > 0) {
const candidate = bytes.subarray(0, end).toString("utf8");
if (byteLength(candidate) === end) return candidate;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 解码后字节数相同不代表 UTF-8 边界完整

truncateUtf8Head("😀", 3)实际返回U+FFFD替换字符:四字节emoji的前三字节解码成一个三字节replacement,恰好满足byteLength(candidate) === end,因此提前返回了并非原文前缀的字符串。真实4096预算下,4093个ASCII字符后接emoji也会出现同样损坏。现测试只有三字节中文,漏掉了此情形。请按codepoint边界回退或采用严格解码验证,并覆盖四字节字符各切割位置;不要让有界裁剪改写原文内容。

@tt-a1i

Copy link
Copy Markdown
Collaborator

当前 head b3d7d46 已复审,Changes Requested。旧的缓存失败阻塞settlement、无缓存上限、canonical/projection混用已有实质修复;新确认4项P2:工具分类遗漏、Mac缓存路径不可用、超长单行无法恢复、四字节UTF-8截断产生替换字符。check/test及必需CI当前均非绿色,详见 #238 (review) 。建议在现有边界内逐项修,不增加新框架。本轮未改源码或合并。

Keep canonical settlement separate from bounded projection, persist artifacts fail-soft, and recover through subagent_result(id, offset, limit) instead of cache pathnames.
Keep exact-result paging, UTF-8 truncation, Darwin cache paths, and
child-tool classification within the existing subagent surface.
@smf-h
smf-hforce-pushed the fix/subagents-snapshot-180 branch from ba54ec5 to 781775eCompareSeptember 1, 2026 04:34

@tt-a1itt-a1i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

复审 exact head 781775e9acf419d34bff904c1e1353229251202e。目标是给 Direct Subagent snapshot 建立聚合 UTF-8 硬上限,同时保留 exact result 的恢复能力;价值是避免长 Session 的模型/UI read model 持续膨胀。当前提交已经修好工具分类、四字节 UTF-8 截断和超长单行分页,但仍有跨平台恢复、fail-open 和生命周期问题。

Standards

2 项:projection rebuild 失败会回退到完整 canonical snapshot,硬资源上限 fail-open;artifact persistence 用未受 Session Scope 管理的裸 setTimeout,shutdown 不取消/等待,且测试把 onSettled 内断言异常吞掉了。

Spec

2 项:exact artifact 现在仅支持 Linux,macOS 上超过 1 MiB 的终态结果不可恢复;Issue #180 要求的旧版/超大 persisted snapshot restore 边界仍没有实现,当前 projection 只存在于进程内 manager。另有 line-offset 转 byte cursor 时文本提示的 cursor 不是绝对值。

验证:macOS exact checkout bun run check 通过;manager/snapshot/result-artifact 专项 47 passed、14 个 Linux-only skipped;独立分页复现得到文本 next 38,结构化 absolute nextByteOffset=45。GitHub Node 22/24 仍各失败 2 项,因此必需 gate 未通过。建议先缩小并闭合现有边界,不要继续扩充存储框架。

const RESULT_CACHE_RECOVERY_NAME =
/^\.retention-lock\.recovery\.[0-9a-f-]{36}$/iu;
const RESULT_ARTIFACT_TEMP_NAME = /^\.[a-f0-9]{64}\.[0-9a-f-]{36}\.tmp$/u;
const SUPPORTED_DESCRIPTOR_PLATFORMS = new Set(["linux"]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 这不是修复 Darwin cache,而是把 exact-result artifact 限定为 Linux。macOS 上 persist 会抛出 unsupported,read 会变成 miss;manager 又只保留 1 MiB finalText,所以更大的 settled result 会被 subagent_result 明确判为 exact unavailable。Issue #180 要求 exact result artifacts 保持可恢复,PR body 也未披露 Linux-only。最小处理可以明确收窄 PR/Issue 范围并保留 #180 未完成;若仍声称 Fixes #180,则需要经验证的 Pi-native/平台安全恢复机制。

return true;
} catch {
// Never expose a projection from an older lifecycle state after a
// failed rebuild; readers fall back to the canonical snapshot.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] 投影重建失败时清空所有 bounded projection,而 view.list/get 随后用 entry.projection ?? entry.snapshot 回退到完整 canonical snapshot。资源上限自身一旦失败,模型/UI 就收到未受 aggregate budget 限制的数据,属于 fail-open。请保留最后一个安全 projection 或返回明确的有界失败状态,不能把 canonical full snapshot 当普通 reader fallback。

// Artifact persistence is optional recovery work. Schedule it only
// after settlement has notified waiters and hooks, so a slow or broken
// writer can never hold the lifecycle path or a concurrency slot.
setTimeout(() => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 这个裸 setTimeout 不属于 manager Scope;disposeAll() 不取消也不等待它,所以 Session 关闭后仍可能写 cache、修改 entry 并触发 notify。它也保证 onSettled/delivery clone 发生在 artifact 关联之前。当前“persisted before settlement”测试在 onSettled 里抛出的断言会被生产代码 catch 掉,因此是假阳性。请跟踪并在 teardown 取消/有界等待该任务,同时用不会被生产 catch 吞掉的外部观测验证顺序。

);
}

const noticeFor = (next: number) => `\n[page truncated; next ${next}]`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] line-offset 页面截断时,这个 notice 使用 page-local end,但返回的 nextByteOffset 是加上 pageStartByte 的绝对 cursor。复现 prefix\n + 长行、offset:1,maxBytes:64:正文提示 next 38,details 返回 nextByteOffset:45。Pi 提供给模型的可靠正文不能指向另一个位置;请让提示使用 absolute cursor,或删掉正文中的数值并只暴露一个可消费的权威 cursor。

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:subagentsSubagent delegation, skills, or tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

subagents: 聚合 snapshot 缺少统一 byte 上限

3 participants

@smf-h@tt-a1i@somewan820