Skip to content

fix(runtime): harden UTF-8 continuations and stream timeouts - #1129

Merged
mergify[bot] merged 4 commits into
matrixorigin:mainfrom
gouhongshen:codex/fix-output-cap-continuation-utf8
Oct 24, 2026
Merged

mergify[bot] merged 4 commits into
matrixorigin:mainfrom
gouhongshen:codex/fix-output-cap-continuation-utf8

Conversation

@gouhongshen

@gouhongshen gouhongshen commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Capability assessment: Unchanged: Fixes UTF-8 output-cap continuation merging and adjusts the existing stream semantic-progress timeout default; capability availability, runtime profiles, model output limits, and tool contracts are unchanged; also patches the existing ctutils dependency without changing capability contracts.

Summary

修复两类生产智能体运行失败:输出上限续写合并中的 UTF-8 panic,以及供应商缓冲长输出时被过短的语义进展阈值提前中断。

  • 按请求,以 c048e2435150af663c632dc4098b636868b36635 为精确代码基线;不带入该提交之后 main 的其他修改。

  • merge_output_cap_continuation 原先逐字节枚举重叠长度,并直接执行 &continuation[..overlap]。中文、破折号、emoji 等多字节字符会让候选位置落在字符内部,触发 not a char boundary panic。

  • 现在仅在合法 UTF-8 字符边界比较重叠片段,保留最长重叠优先、512 字节扫描上限及原有拼接规则。正文和 reasoning 的两个调用点共用这一修复。

  • 新增 4 个续写合并单元测试,覆盖与生产相同的 index 169/破折号位置、中文/emoji 去重、511/512/513 字节边界、无重叠、空文本和完整重放。

  • 复用现有 HTTP provider 回归测试,改用中文和破折号覆盖真实 finish_reason=length → 续写请求 → 合并输出的 Server 执行路径。

  • 将既有 ASTRA_LLM_SEMANTIC_PROGRESS_TIMEOUT_S 的默认值由 120 秒调整为 300 秒,容忍供应商暂时只发送 SSE 心跳、随后集中返回长工具参数的情况。OpenAI-compatible 和 Anthropic 共用该默认值;不改变任何解析或重试逻辑。

  • 保持单次逻辑模型调用总预算默认 300 秒以及原有收尾预留不变;默认 provider 工作窗口约 290 秒。心跳不刷新语义计时,不能延长总预算;显式配置的语义进展超时仍然优先生效。

  • 新增默认值断言及两组虚拟时间回归,覆盖两个协议下的 180 秒心跳后完整工具参数交付,以及只有心跳时仍在 provider 工作预算耗尽后结束。更新配置参考中的实际配置名、默认值和预算边界。

  • 仅更新 Cargo.lock 中既有传递依赖 ctutils 0.4.3 → 0.4.4 的版本及校验和,修复 RUSTSEC-2026-0385 导致的 CI 安全扫描失败;没有连带更新其他 crate,也没有新增或扩大 advisory 忽略规则。

Related issue

无独立 issue;来自生产故障排查。提交中的复现和回归仅使用合成文本,不包含生产对话、账号、供应商凭证或原始流数据。

Change type

  • Feature
  • Bug fix
  • Documentation
  • Refactor or performance improvement
  • Test
  • Build, CI, or maintenance

User and compatibility impact

多字节文本续写不再因字符串切片 panic 而导致任务失败。ASCII、空文本、空白开头、完整重放和去重规则保持不变。

没有显式设置语义进展超时的部署会从 120 秒采用 300 秒默认值,减少缓冲输出造成的提前中断;真正停滞的请求也可能等待更久,但仍受现有总预算约束。这不等于在最后一个 token 后保证额外等待完整 5 分钟,也不保证供应商一定成功。已有超时覆盖值保持不变。

没有新增 fallback、重试、API、配置项、依赖或数据库 schema,不需要数据迁移;既有传递依赖 ctutils 仅升级到安全修复版 0.4.4;不把心跳当作有效回答或工具参数,不改变工具执行边界。

Architecture and complexity delta

  • Canonical owners changed or extended: 只修正 crates/runtime/src/server/server_loop_host.rs 中既有的 merge_output_cap_continuation,以及 crates/runtime/src/turn/llm/client.rs 中两个流式协议共用的语义进展超时默认值;不新建平行实现。
  • Existing implementations and callers searched: 检查了正文和 reasoning 合并调用点、既有 output-cap 单测及 HTTP fixture、两个协议的 collector、既有语义进展/idle/总预算/取消回归,以及实际环境变量读取路径。
  • 回归测试复用现有 Server/provider/ledger fixture 和流式 collector,通过虚拟时间验证长静默,不新增运行时状态、计时器或重试机制。
  • 代码范围为上述两个实现文件、crates/runtime/tests/cache_provider_matrix_e2e.rs、docs/reference/configuration.md,以及仅更新 ctutils 版本与校验和的 Cargo.lock。

Verification

  • 原 UTF-8 复现:在精确基线 c048e243… 上,将原始合并函数与同一组回归测试单独编译运行,旧实现 3 项失败、2 项通过;复现 end byte index 169 is not a char boundary; it is inside '—' (bytes 167..170 of string)。
  • 原 PR 验证:CARGO_BUILD_JOBS=2 CARGO_INCREMENTAL=0 cargo test --locked -p astra-runtime --features e2e-hooks --lib --test cache_provider_matrix_e2e output_cap_ -- --nocapture:18 项相关单测 + 1 项 HTTP provider 链路测试通过。
  • 超时回归红/绿验证:保留旧 120 秒默认值运行 cargo test --locked -p astra-runtime --features e2e-hooks --lib default_semantic_progress_timeout -- --nocapture,新增 3 项测试均按预期失败,捕捉 180 秒缓冲交付被 SemanticProgressTimeout { elapsed_ms: 120000 } 提前中断;调整为 300 秒后通过。
  • 本次完整相关验证:CARGO_BUILD_JOBS=2 CARGO_INCREMENTAL=0 cargo test --locked -p astra-runtime --features e2e-hooks --lib --test cache_provider_matrix_e2e -- default_semantic_progress_timeout openai_reasoning_alias_ keepalives_after_progress user_cancel_wins_the_semantic_progress_deadline_race continuous_reasoning_cannot_bypass_provider_work_budget anthropic_reasoning_cannot_bypass_provider_work_budget reasoning_only_stream_uses_idle_watchdog whitespace_only_text_is_not_semantic_progress output_cap_ --nocapture:31 项单测 + 1 项 HTTP provider 链路测试通过。
  • rustfmt --edition 2024 --check crates/runtime/src/turn/llm/client.rs crates/runtime/src/server/server_loop_host.rs crates/runtime/tests/cache_provider_matrix_e2e.rs:通过;git diff --check:通过。
  • 依赖安全修复:cargo update -p ctutils --precise 0.4.4,确认仅修改该 crate 的版本与校验和;cargo audit 使用最新 RustSec 数据库通过,cargo audit --no-fetch --json 确认 0 个未忽略漏洞,保留原有 4 条 allowed warnings,不修改 .cargo/audit.toml。
  • 升级后的离线回归:重跑上述完整相关验证命令,31 项单测 + 1 项 HTTP provider 链路测试通过。
  • 本地 Docker daemon 未运行,未在本地执行真实 MatrixOne 的连接/认证及 fresh-Astra 兼容测试;不宣称这些验证已经完成,由推送后的 Test: MOI fresh-Astra compatibility CI 使用隔离 MatrixOne 验证。
  • Public entrypoints exercised: 既有 run_agentic_loop_with_host + 本地 OpenAI-compatible HTTP provider fixture;超时回归直接经过 OpenAI-compatible/Anthropic SSE parser 和流式 collector,不调用真实模型。
  • Unhappy paths exercised: 多字节非法切点、重叠边界、只有心跳时的硬预算,以及既有心跳不刷新语义进展和取消回归。
  • Database verification: 不适用,无 schema、SQL、事务或迁移修改;测试使用内存 ledger 和合成 SSE,不连接 QA/生产数据库。未运行全量 gates。

Final checklist

  • I added or updated tests at the layer that owns the behavior, or explained why no test is needed.
  • I updated public or design documentation for contract changes, or the change needs no documentation update.
  • I checked the diff for credentials, private URLs, customer data, generated files, and other sensitive information.
  • The PR title follows the repository's Conventional Commit format.

@gouhongshen gouhongshen changed the title fix(runtime): avoid UTF-8 panic in output-cap continuations fix(runtime): harden UTF-8 continuations and stream timeouts Oct 23, 2026
gouhongshen added a commit that referenced this pull request Oct 23, 2026
Use the complete code tree from PR #1129 head 630f7bf.
Preserve prior moi-dev history as the first parent and the PR head as the second parent.
@gouhongshen
gouhongshen marked this pull request as ready for review October 23, 2026 20:42

@XuPeng-SH XuPeng-SH 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.

Independent manual deep review of ab6892b8dd3246d623f08068dee7900d7fc3064a (all four commits and all five changed files). No actionable regression found; recommend approval.

I traced the Server lifecycle/agentic-loop entrypoint through the real provider client, both text/reasoning continuation callers, SSE parsing and collection, and durable admission/settlement ownership. The UTF-8 guard preserves the longest valid overlap, the 512-byte search bound, and existing whitespace/full-replay/no-overlap behavior. Its comparison cost stays bounded by the existing scan, with output allocation proportional to the merged text.

The shared semantic-progress default reaches OpenAI-compatible, Anthropic and Bedrock. Positive configuration overrides still win. Meaningful text/reasoning and authorized advancing tool delivery retain their existing liveness rules; heartbeats do not extend the semantic or invocation-wide deadline. The default logical budget still leaves 290s of provider work plus a 10s settlement reserve; explicit execution/run budgets retain their existing ownership. Longer stalled waits retain provider/admission capacity and continue existing lease renewals, which is the operational cost of this policy change. Cancellation, delivery-unknown settlement, retry restrictions and resource-release ownership are preserved.

The lockfile changes only ctutils 0.4.3 → 0.4.4 and its checksum. I verified the downloaded package checksum and RUSTSEC-2026-0385's patched range; audit-ignore policy is unchanged.

Scope clarification: the actual merge-base is target 8cbabcce326e4cdb8f1f84cf5f5a37964ae242fd. The head tree exactly equals Git's automatic merge of 630f7bf9 with that target, retaining #1120. I found no unintended reversal/omission. Please update the stale description claiming that all post-c048 main changes are excluded; this is nonblocking.

Independently executed validation:

  • Extracted exact old/head continuation helpers: old 1 passed / 5 failed (including the byte-169 em-dash panic); head 6 passed, including 672,400 generated Unicode pairs, multibyte boundaries around 512, longest overlaps, whitespace and long full replays.
  • Exact-source configuration getter: unset, positive/trimmed overrides, zero, negative, malformed and empty values behaved as expected.
  • Rustfmt on all three changed Rust files and git diff --check passed.
  • cargo test --locked -p astra-runtime --features e2e-hooks --lib --test cache_provider_matrix_e2e with focused output-cap/semantic-timeout/reasoning/cancellation filters: 31 unit tests + 1 real HTTP/Server/WAL test passed.
  • 30 additional existing tests passed from that same exact-head unit-test executable: Bedrock collectors, response-start versus stream/work deadlines, cancellation/admission/retry races, delivery-unknown settlement, user/session fairness and capacity release.
  • With only ASTRA_LLM_SEMANTIC_PROGRESS_TIMEOUT_S=120 set in a disposable test subprocess, all three new timeout tests failed as expected, including SemanticProgressTimeout { elapsed_ms: 120000 } before buffered delivery at 180s. This emulates the old timeout value; it is not a claim that I ran the entire old runtime. The unmodified head defaults passed all three.

Line delta (comments/blanks included within each category; no pure moves):

Category Added Deleted Net
Implementation 8 9 -1
Configuration 0 0 0
Dependency lockfile 2 2 0
Tests 206 5 +201
Documentation 1 1 0
Total 217 17 +200

Immediately before submission I revalidated: open/non-draft, same head/base, authenticated XuPeng-SH distinct from the author, and no existing reviews or inline comments. Exact-head checks (fully enumerated): 36 success, 2 skipped, 1 neutral, from GitHub Actions (App 15368) and Mergify (App 10562); no classic commit-status contexts were returned. Both accessible inherited-inclusive rulesets were read. Classic branch protection and required-context configuration remain unknown; the previously denied administration read was not retried. This approval is a code review, not certification of merge gates.

Limits: local validation used synthetic text/SSE and loopback HTTP with an in-memory ledger. I did not run paid/live models, production/shared database access, a multi-user load test, the full workspace suite, or a fresh cargo-audit scan. CI results and author-reported validation are distinct from the independently executed checks above.

@mergify mergify Bot added the queued label Oct 24, 2026
@mergify
mergify Bot merged commit 9e5e201 into matrixorigin:main Oct 24, 2026
39 of 40 checks passed
@mergify

mergify Bot commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • ✅ Entered queue — 2026-10-24 00:42 UTC · Rule: main · triggered by rule Automatic queue on approval for main
  • ✅ Checks skipped · PR is already up-to-date
  • ✅ Merged — 2026-10-24 00:43 UTC · at 9e5e2019fe580536fa8b68a0452916e69ab113ed · squash

This pull request spent 37 seconds in the queue, with no time running CI.

Required conditions to merge
  • #review-threads-unresolved = 0 [🛡 GitHub branch protection]
  • github-review-approved [🛡 GitHub branch protection]
  • any of [🛡 GitHub branch protection]:
    • check-success = Check PR title
    • check-neutral = Check PR title
    • check-skipped = Check PR title
  • any of [🛡 GitHub branch protection]:
    • check-success = check
    • check-neutral = check
    • check-skipped = check
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: core crates + bridge hooks
    • check-neutral = Test: core crates + bridge hooks
    • check-skipped = Test: core crates + bridge hooks
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: astra-runtime
    • check-neutral = Test: astra-runtime
    • check-skipped = Test: astra-runtime
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: astra-cli (edge-fs-tools)
    • check-neutral = Test: astra-cli (edge-fs-tools)
    • check-skipped = Test: astra-cli (edge-fs-tools)
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: astra-cli (edge-git-gix)
    • check-neutral = Test: astra-cli (edge-git-gix)
    • check-skipped = Test: astra-cli (edge-git-gix)
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: astra-cli (edge-rest)
    • check-neutral = Test: astra-cli (edge-rest)
    • check-skipped = Test: astra-cli (edge-rest)
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: astra-cli (edge-shell)
    • check-neutral = Test: astra-cli (edge-shell)
    • check-skipped = Test: astra-cli (edge-shell)
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: astra-cli (non-edge)
    • check-neutral = Test: astra-cli (non-edge)
    • check-skipped = Test: astra-cli (non-edge)
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: online (core)
    • check-neutral = Test: online (core)
    • check-skipped = Test: online (core)
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: online (integration)
    • check-neutral = Test: online (integration)
    • check-skipped = Test: online (integration)
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: turn-core + services + plan
    • check-neutral = Test: turn-core + services + plan
    • check-skipped = Test: turn-core + services + plan
  • any of [🛡 GitHub branch protection]:
    • check-skipped = web (typecheck, test, build)
    • check-neutral = web (typecheck, test, build)
    • check-success = web (typecheck, test, build)
  • any of [🛡 GitHub branch protection]:
    • check-skipped = Astra SDK (typecheck, test+coverage, build)
    • check-neutral = Astra SDK (typecheck, test+coverage, build)
    • check-success = Astra SDK (typecheck, test+coverage, build)
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: macOS workspace coordination
    • check-neutral = Test: macOS workspace coordination
    • check-skipped = Test: macOS workspace coordination

@mergify mergify Bot removed the queued label Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants