fix(stream): bound and coalesce live tool output previews - #947
Conversation
Verified: crates/aionui-ai-agent/tests/live_tool_output_preview.rs with Codex CLI 0.147.0 and a 1 MiB synthetic command. The old main fails the 1 MiB retained-preview bound; the rebuilt patched pump passes and preserves the final result. The live test remains opt-in and uses a disposable CLI home.
|
Added real-agent/reference evidence to address the verification requirements in Commit At the same 512 KiB synthetic-output checkpoint, old main retains 17,039,360 bytes of preview payload and fails the <=1 MiB assertion; the rebuilt patched pump retains 393,216 bytes and passes. After the reader resumes slowly, the patched run receives all 1,048,576 bytes / 128 deltas, preserves the final result's exact length and SHA-256, completes successfully, and emits no delayed preview after completion. The PR body includes reproduction details and evidence boundaries. The description now also separates the initial PR validation from the later v0.1.72 backport deployment. It does not claim the main-based PR HEAD was deployed, or that local live testing proves the production/browser load case. Local validation for this head is complete: affected-crate tests/Clippy and full Could a maintainer please approve the upstream CI run for this updated head? It is |
Summary
Fixes #946.
Bound and coalesce live tool-output previews in the direct-session event pump. Previously, every
ToolOutputDeltacloned the entire cumulative string into a 512-event broadcast queue. A slow consumer could retain hundreds of large cumulative copies.Behavior
ToolResultoutput through the existing path, without adding truncation. Discard pending preview state before forwarding a result.No wire-format, API, DB schema, frontend, or deployment-file changes in this PR.
Regression evidence
The new
tool_output_previewintegration test runs the realSessionAgentTaskpump over a controlled syntheticSessionBackend, with deterministic event acknowledgements and a paused Tokio clock. It is not a mock of the accumulation branch and does not require a live CLI or private data.The test was added and run before changing the pump on main
5a447e15bc32c4295f437d0ffd530b9992911836:The original assertion failed on
100728832 > 1048576. With the fix, resuming the subscriber delivers the latest bounded tail, not an old backlog.The eight-session test sends 5 MiB per session, with readers consuming only every 20 producer ticks. Maximum retained preview payload was 8,388,608 bytes across all eight sessions. On a local macOS arm64 run of the test binary,
/usr/bin/time -lreported 31,588,352 bytes maximum RSS. This RSS is an observation of the synthetic test process, not a CI threshold or a production/VPS measurement.Real-agent validation (2026-08-30 UTC)
To cover the outward/reference requirement in
AGENTS.md, added the opt-in live testcrates/aionui-ai-agent/tests/live_tool_output_preview.rs. It uses the real Codex CLI 0.147.0, through productionRealSpawner -> CodexConnection -> SessionAgentTask. Its observation wrapper forwards commands and events unchanged; it does not generate backend events.The real agent runs a synthetic UTF-8, no-newline output generator in a disposable workspace. After 512 KiB, the generator waits for a reader checkpoint. Only the outer subscriber is suspended; the backend and pump continue consuming. After measuring the retained preview payload, the subscriber resumes at one event per 200 ms while the command completes its full 1 MiB output.
5a447e1d92b4d5The patched run then received 1,048,576 bytes in 128 real output deltas and one final result of 1,048,576 bytes. It compares the final result's length and SHA-256 before/after the pump, requires terminal success, bounds each delivered preview, detects subscriber lag, and checks for delayed previews after completion. All passed. The old run intentionally stops at the failing checkpoint assertion; it is not a full-turn success claim for old main.
These are separate real-agent runs with the same generator, not a replay of identical wire timing. Counts below the fixed bound can vary with timing. The changed crate was explicitly rebuilt after switching reference worktrees: an intermediate shared-cache run was discarded, not counted as patched evidence. CLI behavior evidence is this executed live test and its aggregate output, not a guessed wire format. The binary's own
--version,app-server --help, and generated JSON schema were also inspected.To reproduce (requires an existing Codex login; spends model tokens):
Use separate Cargo target directories for the two worktrees, or rebuild
aionui-ai-agentwhen switching. The test is ignored by default: normal CI does not receive credentials, and a skipped live test is not a live validation. No credentials, private hostnames, real conversation content, or raw CLI session identifiers are included here or in the test.This exercises the real CLI and the pump, not an authenticated browser or network WebSocket under load. The separate deterministic relay/SQLite test below proves final persistence.
Tests
StreamRelay, WebSocket event bus, and in-memory SQLite; it verifies full final output, terminal status, name, arguments, and parent attribution.Initial validation at
d92b4d5on macOS arm64 / Rust 1.95.0:cargo test -p aionui-ai-agent -p aionui-conversation— passed (pre-existing ignored tests remain ignored).cargo clippy --all-targetsandcargo clippy --workspace -- -D warnings— passed.cargo fmt --all -- --checkand migration immutability check — passed.just push— passed its full gate: 9,338 tests passed, zero failures, 52 skipped; branch pushed successfully.cargo test --workspace --quiet— completed successfully: 9,338 passed, zero failures, 53 ignored, including doctests in this count.Follow-up validation for the test-only commit
1af08fd3e087a1ef1337f0e17943b46a84234d08:cargo test -p aionui-ai-agent -p aionui-conversation --quiet— 1,624 passed, zero failures, 13 ignored. The new live test's separate execution is not hidden in this aggregate.cargo clippy -p aionui-ai-agent -p aionui-conversation --all-targets -- -D warningsand format/diff checks — passed.cargo test --workspace --quiet— 9,338 passed, zero failures, 54 ignored, across 249 groups including doctests.just push— migration immutability, full-workspace code checks, formatting, and 9,338 tests passed / 53 skipped; branch pushed successfully. The gate did not change the commit.The upstream Linux CI is separate from these local results. The current run for
1af08fdisaction_required, awaiting maintainer approval, with zero jobs executed, as checked on 2026-08-30 UTC after the push. The earlier run ford92b4d5was gated the same way. Local checks do not substitute for upstream CI. No skipped test is counted as verified.Reproduce the focused checks with:
Limits
The bound covers queued live-preview output payload, excluding metadata, receiver-held clones, terminal results, and other event classes. Active tools also retain at most 64 KiB each. This is not a global session/process memory cap, a durable output archive, or a general WebSocket backpressure redesign.
The final result remains whatever the backend supplies; this PR cannot restore output already omitted upstream. Existing terminal-event delivery policy is unchanged. The tests prove the targeted amplification and lifecycle behavior, not the exact heap attribution of the reported production incident.
Deployment evidence is separate
The initial PR validation did not include a deployment. Later, on 2026-08-30 at 21:18 UTC, the same preview fix was backported onto AionCore v0.1.72 and installed on one VPS with AionUi 2.1.61 unchanged. This was a separate backport build, not the main-based PR HEAD, and no deployment files are part of this PR.
That deployment was checked for running-binary identity, startup, unchanged database counts/identity fingerprints and SQLite integrity, successful authenticated HTTP requests, and a short observation with agent teams active. Those checks do not prove a massive-output/slow-reader browser scenario on the VPS. The controlled real-agent test above ran locally, not against those production teams. No authenticated browser canary or comprehensive production-memory guarantee is claimed; nothing was merged automatically.