Skip to content

perf(adk): 降低嵌套 AgentTool checkpoint 放大 - #1234

Open
shentongmartin wants to merge 19 commits into
mainfrom
fix/agenttool-checkpoint-linear-size
Open

perf(adk): 降低嵌套 AgentTool checkpoint 放大#1234
shentongmartin wants to merge 19 commits into
mainfrom
fix/agenttool-checkpoint-linear-size

Conversation

@shentongmartin

@shentongmartin shentongmartin commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Problem

A Child model payload was serialized repeatedly across Runner state, AgentTool bridge state, compose subgraphs, ToolsNode rerun state, and legacy interrupt metadata. A 320 KiB Child response produced a 6.97 MB outer checkpoint, while deeper AgentTool nesting multiplied the same logical data again.

Solution

The checkpoint now follows explicit ownership boundaries:

  • AgentTool bridge state is the sole owner of Child Runner bytes.
  • Each compose subgraph stores only its own interrupt state; ancestors retain routing metadata for targeted resume.
  • ToolsNode writes a versioned compact rerun state and references ToolCalls already owned by the same checkpoint when the match is exact.
  • Runner projection replaces exact duplicate messages and successful tool results with validated references to canonical compose-owned values.
  • Versioned gob sentinels make unsupported new formats fail loudly, while frozen checkpoints from main@60e1d992 remain readable.

Projection is conservative: missing IDs, conflicting IDs, or values that cannot preserve exact ResumeInfo.Data remain inline. Hydration validates versions, counts, coordinates, digests, ownership paths, and routing ancestry before execution, then deep-clones restored values to avoid mutable aliases.

Decisions

Size thresholds are measurements, not phase gates. All planned phases are implemented even where a local optimization alone did not meet an intermediate target. Inputs and channel values remain inline when replacing them cannot preserve byte-identical resume data. ToolsNode pre-handlers keep their existing lifecycle; persisted effective ToolCalls override the resumed input after the handler runs.

Key Insight

Compose subgraphs and AgentTool children are different restore boundaries. A subgraph shares the parent resume scope and owns state at its deepest routing checkpoint. An AgentTool child restores an independent Runner checkpoint. Preserving that distinction removes repeated payload ownership without weakening targeted resume semantics.

Result

  • 320 KiB nested AgentTool: 6,972,730 B393,641 B
  • 1 MiB nested AgentTool Content: 22,111,550 B1,114,536 B
  • 1 MiB ToolCall Arguments: 8,409,917 B single-agent baseline → 1,114,527 B nested final
  • 320 KiB Content at depth 0/1/2/3: 357,235 → 393,643 → 444,316 → 526,734 B

The remaining growth is approximately linear with payload size, depth, and sibling count instead of multiplicative duplication.

Verification

  • go test ./...
  • go test -race ./adk/... ./compose/...
  • GOTOOLCHAIN=go1.18.10 go test -race ./adk ./compose ./internal/core
  • golangci-lint run --new-from-rev=origin/main ./...
  • Frozen legacy fixtures resume through both the current reader and the v0.9.18 subprocess reader
  • Invoke/Stream, cancellation, targeted parallel resume, sibling re-interrupt, ToolCalls, EnhancedTool results, corruption rejection, and size matrices are covered

问题

同一份 Child model payload 会被重复写入 Runner state、AgentTool bridge state、compose subgraph、ToolsNode rerun state 和 legacy interrupt metadata。实测 320 KiB 的 Child 输出会生成 6.97 MB checkpoint,多层 AgentTool 嵌套还会继续重复放大。

方案

checkpoint 现在按明确的所有权边界保存状态:

  • AgentTool bridge state 是 Child Runner bytes 的唯一 owner。
  • 每个 compose subgraph 只保存自己拥有的 interrupt state,祖先只保留 targeted resume 所需的路由索引。
  • ToolsNode 写入带版本的 compact rerun state;能够精确匹配时,ToolCalls 引用同一 checkpoint 已有的数据。
  • Runner projection 将重复 message 和成功 tool result 替换为经过校验的引用,canonical value 仍由 compose state 持有。
  • 新格式通过带版本的 gob sentinel 让旧 reader 明确报错;main@60e1d992 生成的冻结 checkpoint 仍可读取。

Projection 只处理能够严格证明相同的数据。缺少 ID、ID 冲突或无法保证 ResumeInfo.Data 字节级一致时继续 inline。恢复前会校验版本、数量、坐标、digest、owner path 和 routing ancestry,并对恢复对象做深拷贝,避免可变别名。

决策

体积目标只作为测量值,不再作为中止后续阶段的门槛,因此计划中的全部阶段都已实现。Inputs 和 channel value 在无法保持恢复数据完全一致时继续 inline。ToolsNode pre-handler 生命周期保持不变,恢复时由持久化的有效 ToolCalls 覆盖 handler 输出。

关键认识

compose subgraph 与 AgentTool Child 是两个不同的恢复边界:subgraph 共享父级 resume scope,state 归最深路由 checkpoint 所有;AgentTool Child 则恢复独立的 Runner checkpoint。只要保持这一区分,就能消除重复 payload,同时不破坏 targeted resume。

结果

  • 320 KiB 嵌套 AgentTool:6,972,730 B393,641 B
  • 1 MiB 嵌套 AgentTool Content:22,111,550 B1,114,536 B
  • 1 MiB ToolCall Arguments:8,409,917 B 单 Agent 基线 → 1,114,527 B 嵌套最终值
  • 320 KiB Content 深度 0/1/2/3:357,235 → 393,643 → 444,316 → 526,734 B

剩余增长随 payload、深度和并行宽度近似线性,不再是重复序列化造成的乘法放大。

验证

  • go test ./...
  • go test -race ./adk/... ./compose/...
  • GOTOOLCHAIN=go1.18.10 go test -race ./adk ./compose ./internal/core
  • golangci-lint run --new-from-rev=origin/main ./...
  • 冻结 legacy fixture 同时通过当前 reader 和 v0.9.18 子进程 reader
  • 覆盖 Invoke/Stream、取消、并行 targeted resume、兄弟分支 re-interrupt、ToolCalls、EnhancedTool result、损坏数据拒绝与体积矩阵

Capture main checkpoint bytes and verify both current and v0.9.18 readers can resume them before changing persisted formats.

Change-Id: I81068999292d405449848b0afbe37e87b44572d5
Store the child runner checkpoint once in versioned AgentTool state and propagate only public interrupt contexts to the parent.

Change-Id: I553ec1a656bcbc75da1c11e479de94a3137c3377
Merge child-owned interrupt state without overwriting consumed entries and validate versioned layout metadata before resume.

Change-Id: I9b866d6f3cff4aee3cacc3f7b6ab0dafe9fcd970
Write sparse versioned checkpoint state maps, guard forward-incompatible readers with a gob sentinel, and retain full routing indexes for targeted resume.

Change-Id: Iee06fa136cbc4e35dea3fcc3706980d68c7098a2
Persist each interrupt state only in its owning graph checkpoint and add version sentinels that fail loudly in older SDKs.

Change-Id: Idbba32d73bdc4ddc826d15de9e01c7b42de42d26
Add a versioned reader for minimal tool-call checkpoint state while preserving the legacy writer after the compact-size gate failed.

Change-Id: I9685e3d8b35bcb48e75124b3ced5733f396ae263
Change-Id: I3ad900fee1056276a745ba7498b941598858df10
@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.29167% with 244 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.07%. Comparing base (60e1d99) to head (5b61af6).

Files with missing lines Patch % Lines
adk/checkpoint_projection.go 87.79% 76 Missing and 64 partials ⚠️
compose/graph_run.go 80.12% 16 Missing and 16 partials ⚠️
compose/checkpoint.go 83.60% 15 Missing and 15 partials ⚠️
adk/checkpoint_projection_tools.go 91.30% 9 Missing and 9 partials ⚠️
compose/tool_node.go 90.90% 7 Missing and 7 partials ⚠️
adk/interrupt.go 60.00% 2 Missing and 2 partials ⚠️
internal/core/address.go 87.50% 2 Missing and 2 partials ⚠️
adk/agent_tool.go 92.30% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1234      +/-   ##
==========================================
+ Coverage   83.71%   84.07%   +0.35%     
==========================================
  Files         162      164       +2     
  Lines       23887    25777    +1890     
==========================================
+ Hits        19998    21672    +1674     
- Misses       2628     2733     +105     
- Partials     1261     1372     +111     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Change-Id: I3fc67c65d981a141007559eba11bc579e591af53
Change-Id: Ieb7cbd93c0f98c04129b1cb21f890b45e61aed4e
Change-Id: I68cdd004ee6d6edb07d486377600c6278f70241c
Change-Id: I16baaf0aea497e2c9f5f26fc071118bacb7d680b
Change-Id: I5deda094c9075928598f702f26ff82b03638e626
Change-Id: Ia9330a0993bd7dd845e9e46496a10b0879606da1
Change-Id: Ib5d3f9fea5f4a7155f456763ea00a84882b5cf98
Change-Id: Ie59d875f0ca03cc16fb8155f7ca18fd7202e615b
Change-Id: I5524d11e527b2e6133936d529a1c014a9c664aa8
Change-Id: I3b7d5b9c7024e8f717ac1e7b88c98a4d221217f6
Change-Id: I21707dfc8539bd6a2f66f85f7fcb28330be0fed2
Reject ambiguous and conflicting persisted state, make corruption errors deterministic, and keep migration and traversal semantics consistent with compact checkpoint references. Strengthen compatibility, attack, size, and regression tests.

Change-Id: I9e860ecece23c3c049b59fa6ce6a28e608967d2b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant