Skip to content

fix(agent): make progress forwarding non-blocking#4356

Open
YOMXXX wants to merge 9 commits into
tinyhumansai:mainfrom
YOMXXX:fix/GH-4269-response-watchdog
Open

fix(agent): make progress forwarding non-blocking#4356
YOMXXX wants to merge 9 commits into
tinyhumansai:mainfrom
YOMXXX:fix/GH-4269-response-watchdog

Conversation

@YOMXXX

@YOMXXX YOMXXX commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Makes progress delta forwarding non-blocking when the bounded progress sink is full.
  • Applies the existing drop-on-full progress policy to top-level and subagent streamed deltas.
  • Keeps final response correctness intact: dropped frames are transient UI/progress deltas; completed provider output still returns through the normal chat result.

Scope note

This PR is defensive hardening for progress-channel backpressure only. It no longer claims to fix #4269. Sanil's reproduction points at an upstream SSE read-side silent stall, not delta forwarding backpressure, so #4269 should stay open for an SSE inactivity-watchdog fix in the provider stream path.

Validation

Previous branch validation before the main merge:

  • cargo fmt --manifest-path Cargo.toml --check
  • git diff --check
  • CARGO_TARGET_DIR=/Users/liguanchen/Desktop/lgc/openhuman-gh4352/target GGML_NATIVE=OFF cargo test --manifest-path Cargo.toml --lib agent::harness::engine::progress::tests:: -- --nocapture
  • CARGO_TARGET_DIR=/Users/liguanchen/Desktop/lgc/openhuman-gh4352/target GGML_NATIVE=OFF cargo test --manifest-path Cargo.toml --lib openhuman::agent::harness::subagent_runner::ops::tests::typed_mode_forwards_child_text_and_thinking_deltas -- --exact
  • CARGO_TARGET_DIR=/Users/liguanchen/Desktop/lgc/openhuman-gh4352/target GGML_NATIVE=OFF cargo check --manifest-path Cargo.toml

After the latest upstream/main merge, PR CI should be the source of truth for the full updated tree.

Impact

  • Runtime impact: progress streaming during agent turns.
  • Tradeoff: when the progress bridge is saturated, transient streamed delta frames may be dropped.
  • No persistence, migration, security, or network dependency changes.

Related


AI Authored PR Metadata

Branch

  • fix/GH-4269-response-watchdog

Behavior Changes

  • Intended behavior change: streamed progress deltas no longer apply blocking backpressure to provider response streaming when the progress bridge is full.
  • User-visible effect: live progress text may skip transient frames under load; final assistant content remains sourced from the completed provider response.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none found.
  • Canonical PR: this PR for progress-forwarder hardening only.

Summary by CodeRabbit

  • Chores
    • Updated CI guard allowlist to recognize newly gated test coverage.
    • Improved pull request quality checks by excluding badge image URLs from link validation to reduce false failures.
  • Tests
    • Refined learning subscriber tests to invoke the subscriber directly and validate handling behavior.
    • Strengthened end-to-end tool execution coverage by asserting clear errors on invalid inputs, then verifying success with valid inputs.

@YOMXXX
YOMXXX requested a review from a team June 30, 2026 16:40
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The CI workflows update feature-gated test and link-check validation. Learning and Composio coverage tests now exercise direct subscriber handling and action-tool argument validation followed by a successful retry.

Changes

Validation and testing updates

Layer / File(s) Summary
Workflow validation updates
.github/workflows/ci-lite.yml, .github/workflows/pr-quality.yml
Adds the Composio action tool module to the feature-gated test allowlist and excludes shields.io badge URLs from lychee validation.
Direct subscriber test invocation
src/openhuman/learning/startup.rs
Imports EmailSignatureSubscriber and directly handles a constructed DocumentCanonicalized event in the learning test.
Action-tool argument validation
tests/raw_coverage/composio_credentials_state_raw_coverage_e2e.rs
Checks the missing-argument contract prompt, then retries with a valid query and asserts success.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested labels: bug, agent

Suggested reviewers: senamakel

Poem

A rabbit checks the gates with care,
Sends badge links hopping through the air.
Events hop straight to where they’re due,
Tools retry with queries true. 🐇

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR explicitly says it does not fix #4269 and leaves the RESPONSE-state hang criteria unmet. Implement the RESPONSE-phase hang fix or watchdog in #4269, then verify the agent progresses or surfaces a recoverable error.
Out of Scope Changes check ⚠️ Warning The CI workflow edits and Composio test changes are unrelated to the research-agent hang fix in #4269. Move the workflow maintenance and Composio coverage updates to a separate PR unless they are required for #4269.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main change: making progress forwarding non-blocking.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhuman/agent/harness/engine/progress.rs`:
- Around line 432-435: The progress forwarder test timeout is too tight and can
flake under slow CI scheduling. Update the timeout used around the forwarder in
the progress test assertions (the one using timeout(Duration::from_millis(...))
and the matching check in the other occurrence) to a slightly larger bound while
keeping the same expectation that the forwarder completes promptly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6c82366b-eaee-4f27-bca5-98b3345d8e21

📥 Commits

Reviewing files that changed from the base of the PR and between 06e9bd1 and 8f4d64e.

📒 Files selected for processing (1)
  • src/openhuman/agent/harness/engine/progress.rs

Comment thread src/openhuman/agent/harness/engine/progress.rs Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 30, 2026

@sanil-23 sanil-23 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.

Thanks for digging into this @YOMXXX. I spent time reproducing #4269 end-to-end against a staging build, and I don't think this PR addresses the actual failure mode — sharing the evidence so we can redirect the fix.

Reproduction

Drove the real research agent via CDP against a staging build (staging-api.tinyhumans.ai), looping deep-research queries (arxiv paper + web search — the issue's own repro shape), watching the core [stream] breadcrumbs and the delta pipeline.

What actually happens

The hang is an upstream SSE stall on the read side, not delta backpressure. Representative trace:

21:40:40  [stream] OpenHuman POST .../chat/completions (stream=true, tools=14)
21:42:40  [stream] streaming chat failed, falling back to non-streaming: error decoding response body

Exactly 120s between the POST and the failure: the SSE body goes silent mid-response, the reader parks on bytes_stream.next().await (compatible_stream_native.rs), and it is only cut when the 120s total request timeout fires (surfacing as error decoding response body), after which the existing fallback-to-non-streaming recovers. I saw this stall class on 2 of the first 3 research runs, plus a sibling "Request timed out." backend error frame — same ~120s silent-stall regime.

Crucially, during the stall there are zero ProviderDeltas in flight — the provider is blocked reading bytes, not sending deltas. So making the delta forwarders non-blocking (this PR) has nothing to act on in that state.

Why this PR doesn't fix it

  • It changes only progress.rs (the delta forwarders). The stall is in compatible_stream_native.rs's read loop, which is untouched here.
  • The wedge it targets requires deltas flooding a stalled sink; the reproduced hang is the opposite — zero output, a silent read.
  • Tracing the consumer chain end to end, the terminal consumer (progress_bridge.rs) always drains (bounded synchronous ledger writes + a non-blocking broadcast publish), so progress backpressure is transient and self-clearing — it can't produce the indefinite hang #4269 describes.
  • The two added tests construct a capacity-1 sink that is pre-filled and never drained — a synthetic condition that doesn't occur in the running system (the bridge drains). "timeout before / pass after" demonstrates the mechanism, but not that it is #4269's cause.

One tradeoff to flag: switching text deltas to drop-on-Full means live streaming can drop token frames under load (final content is still correct via the aggregated response, but streaming fidelity regresses) — a real cost for a path that isn't the observed failure.

Why users see an indefinite hang

On default config the 120s total request timeout rescues the stall (a ~2-minute freeze — "cursor blinks, no output"). But #3856 advises operators to raise OPENHUMAN_INFERENCE_TIMEOUT_SECS up to 3600s for long research turns; with that raised, this exact stall hangs for up to an hour = the reported indefinite hang.

Suggested direction

A per-token inactivity watchdog on the SSE read — reset on every received chunk, ~60–90s, independent of the total request timeout, classified retryable so it falls back cleanly. That bounds the stall regardless of backend behavior and independent of the total-timeout knob operators are told to raise — which the total timeout can't do and this PR doesn't address. Happy to share the fix I'm testing.

Net: I'd suggest not closing #4269 with this PR. The non-blocking forwarder change may be reasonable defensive hardening in its own right, but it doesn't resolve the reproduced hang.

Resolve conflict in the streaming-delta forwarder after upstream moved it from
engine/progress.rs to session/tool_progress.rs and removed SubagentProgress
(sub-agent runs now share TurnProgress + spawn_delta_forwarder).

Re-apply the tinyhumansai#4269 fix on the new structure: the forwarder pushes provider
deltas with a non-blocking try_send (emit_stream_delta / emit_nonblocking)
instead of a blocking send().await, so a saturated UI/progress channel drops
transient delta frames rather than backpressuring the provider stream and
wedging the turn in RESPONSE. Keep the spawn_delta_forwarder regression test;
drop the SubagentProgress variant (type removed upstream, coverage subsumed).
@YOMXXX YOMXXX changed the title fix(agent): avoid RESPONSE stalls on progress backpressure fix(agent): make progress forwarding non-blocking Jul 19, 2026
@YOMXXX

YOMXXX commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

Updated this PR after the latest main merge and sanil's reproduction notes. It no longer claims to close #4269: the title/body now scope it to defensive progress-forwarder hardening only, and #4269 remains open for the SSE read-side inactivity-watchdog fix. Also merged current upstream/main so CI can re-evaluate against the current tree.

@coderabbitai coderabbitai Bot added agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. bug labels Jul 19, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/ci-lite.yml (1)

813-844: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Missing dependency: add rust-feature-gate-smoke to the final CI gate.

The rust-feature-gate-smoke job is missing from the needs array and the results validation matrix. Because it is orphaned from pr-ci-gate, a failure in the feature-gate contract checks (or the ungated asserts) will not block the PR from being merged.

Please add the missing job to ensure the feature-gate smoke test acts as a strict guard.

🛠️ Proposed fix
     needs:
       - changes
       - frontend-checks
       - rust-quality
+      - rust-feature-gate-smoke
       - rust-core-coverage
       - rust-tauri-coverage
       - scripts-tests
       - test-inventory
       - pester-install
       - tinycortex-tests
       - orch-ip-gate
       - feature-forwarding-gate
     if: always()
     runs-on: ubuntu-latest
     timeout-minutes: 15
     steps:
       - name: Require PR CI jobs to pass
         run: |
           set -euo pipefail
           declare -A results=(
             ["Detect Changed Areas"]="${{ needs.changes.result }}"
             ["Frontend Checks"]="${{ needs['frontend-checks'].result }}"
             ["Rust Quality"]="${{ needs['rust-quality'].result }}"
+            ["Rust Feature-Gate Smoke"]="${{ needs['rust-feature-gate-smoke'].result }}"
             ["Rust Core Coverage"]="${{ needs['rust-core-coverage'].result }}"
             ["Rust Tauri Coverage"]="${{ needs['rust-tauri-coverage'].result }}"
             ["Scripts Self-Tests"]="${{ needs['scripts-tests'].result }}"
             ["Test Inventory"]="${{ needs['test-inventory'].result }}"
             ["PowerShell Install Test"]="${{ needs['pester-install'].result }}"
             ["TinyCortex Memory Tests"]="${{ needs['tinycortex-tests'].result }}"
             ["Orchestration IP Gate"]="${{ needs['orch-ip-gate'].result }}"
             ["Feature Forwarding Gate"]="${{ needs['feature-forwarding-gate'].result }}"
           )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci-lite.yml around lines 813 - 844, Add
rust-feature-gate-smoke to the final CI gate’s needs list and to the results
matrix in the “Require PR CI jobs to pass” step, using the job’s result for
validation so feature-gate smoke failures block the PR.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In @.github/workflows/ci-lite.yml:
- Around line 813-844: Add rust-feature-gate-smoke to the final CI gate’s needs
list and to the results matrix in the “Require PR CI jobs to pass” step, using
the job’s result for validation so feature-gate smoke failures block the PR.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c048908d-2e6f-4c2f-88f2-aae0338f4a70

📥 Commits

Reviewing files that changed from the base of the PR and between 37b7c6d and 1f89cab.

📒 Files selected for processing (2)
  • .github/workflows/ci-lite.yml
  • .github/workflows/pr-quality.yml

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 19, 2026
@YOMXXX

YOMXXX commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

Clarifying the latest CodeRabbit pre-merge warnings: that review appears to be scoped to the final CI-maintenance commit only, not the full PR diff. The PR still contains the progress-forwarder hardening in src/openhuman/agent/harness/engine/progress.rs; the later workflow edits only repair shared merge gates after main drift (openhuman/composio/action_tool.rs allowlist and shields.io lychee exclusion), matching the same CI failure seen across the active branches.

The title/body no longer claim to close #4269. That issue remains open for the separate SSE read-side inactivity-watchdog fix called out in sanil-23’s reproduction notes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/openhuman/learning/startup.rs (1)

274-298: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update test name, assertion, and avoid redundant polling.

Now that the test invokes the subscriber directly and bypasses the event bus, the name and assertion message no longer reflect its behavior. Furthermore, since the handle(...).await call completes synchronously rather than relying on async event bus delivery, wait_for_candidates is no longer necessary and introduces an artificial 40ms settling delay.

Consider renaming the test, updating the assertion, and checking the candidate count directly.

♻️ Proposed refactor
-    #[tokio::test]
-    async fn learning_subscriber_fires_with_no_channel_configured() {
+    #[tokio::test]
+    async fn email_signature_subscriber_handles_canonicalized_event() {
         let source_id = unique_source_id("e2e");
         let body = signature_body();
         let expected = parse_signature(&body, &source_id, &source_id).len();
         assert!(
             expected > 0,
             "signature body must yield at least one identity candidate"
         );
 
         let event = DomainEvent::DocumentCanonicalized {
             source_id: source_id.clone(),
             source_kind: "email".to_string(),
             chunks_written: 1,
             chunk_ids: vec![format!("{source_id}-c1")],
             canonicalized_at: 0.0,
             body_preview: Some(body),
         };
         crate::core::event_bus::EventHandler::handle(&EmailSignatureSubscriber, &event).await;
-        let got = wait_for_candidates(&source_id, expected).await;
+        let got = candidates_for(&source_id);
         assert_eq!(
             got, expected,
-            "email-signature subscriber must push the parsed identity candidates \
-             with no channel configured anywhere (`#5003`)"
+            "email-signature subscriber must push the parsed identity candidates"
         );
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/learning/startup.rs` around lines 274 - 298, Update
learning_subscriber_fires_with_no_channel_configured to reflect direct
subscriber invocation rather than event-bus delivery, revise the assertion
message accordingly, and replace wait_for_candidates with a direct
candidate-count check after handle(...).await so no polling delay remains.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/openhuman/learning/startup.rs`:
- Around line 274-298: Update
learning_subscriber_fires_with_no_channel_configured to reflect direct
subscriber invocation rather than event-bus delivery, revise the assertion
message accordingly, and replace wait_for_candidates with a direct
candidate-count check after handle(...).await so no polling delay remains.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 88a3d30d-6cc4-40c6-9418-9bf91608d98a

📥 Commits

Reviewing files that changed from the base of the PR and between 1f89cab and 8e72514.

📒 Files selected for processing (1)
  • src/openhuman/learning/startup.rs

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 19, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/raw_coverage/composio_credentials_state_raw_coverage_e2e.rs`:
- Around line 310-311: Update the initial action_tool.execute call in the
contract prompt test to pass an empty JSON object, ensuring the first execution
omits the query argument before the existing valid-argument retry.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3b49a97c-2cca-464d-a3d8-6d4c9d39c446

📥 Commits

Reviewing files that changed from the base of the PR and between 8e72514 and ac0a271.

📒 Files selected for processing (1)
  • tests/raw_coverage/composio_credentials_state_raw_coverage_e2e.rs

Comment thread tests/raw_coverage/composio_credentials_state_raw_coverage_e2e.rs Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. bug

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

Research agent intermittently hangs mid-run

2 participants