fix(harness): detach memory maintenance from agent call response#2248
fix(harness): detach memory maintenance from agent call response#2248Asaduddin18 wants to merge 3 commits into
Conversation
MemoryMaintenanceMiddleware concatenated the maintenance Mono onto the returned Flux via concatWith, so callers that consume the response to completion (blockLast(), takeLast(1)) waited for the full LLM-based consolidation call to finish -- inflating agent call latency by up to 30s and contradicting documentation that describes this as a background/fire-and-forget step. Detach maintenance via doOnComplete(...).subscribe() instead, so the returned Flux completes as soon as the underlying agent call does, independent of maintenance duration. Also add a timeout to the consolidation LLM call so a hung provider can't tie up a shared boundedElastic thread indefinitely. Fixes agentscope-ai#2225
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
AgentScopeJavaBot
left a comment
There was a problem hiding this comment.
🤖 AI Review
This PR fixes #2225: MemoryMaintenanceMiddleware.onAgent previously concatenated the maintenance Mono onto the response Flux via .concatWith(...), forcing callers (e.g., blockLast(), takeLast(1)) to wait for the full consolidation chain (including LLM call) before receiving the response. Changed to .doOnComplete(() -> Mono...subscribe()) to genuinely detach maintenance as fire-and-forget. Also added .timeout(CONSOLIDATION_TIMEOUT) to MemoryConsolidator.consolidate() to prevent a hung model provider from permanently blocking a boundedElastic thread. Two regression test suites cover async behavior and timeout semantics. The fix is correct, well-scoped, and aligns with the issue description.
Recommendation: COMMENT
AgentScopeJavaBot
left a comment
There was a problem hiding this comment.
🤖 AI Review
This PR fixes #2225: MemoryMaintenanceMiddleware.onAgent previously concatenated the maintenance Mono onto the response Flux via .concatWith(...), forcing callers (e.g., blockLast(), takeLast(1)) to wait for the full consolidation chain (including LLM call) before receiving the response. Changed to .doOnComplete(() -> Mono...subscribe()) to genuinely detach maintenance as fire-and-forget. Also added .timeout(CONSOLIDATION_TIMEOUT) to MemoryConsolidator.consolidate() to prevent a hung model provider from permanently blocking a boundedElastic thread. Two regression test suites cover async behavior and timeout semantics. The fix is correct, well-scoped, and aligns with the issue description.
Recommendation: COMMENT
AgentScope-Java Version
2.0.1-SNAPSHOT
Description
Background:
MemoryMaintenanceMiddleware.onAgentconcatenated the memory-maintenanceMonoonto the returnedFluxvia.concatWith(...). BecauseReActAgent.callInternalends its call chain with.takeLast(1)(which can't emit until the upstream Flux signalsonComplete), any caller consuming the agent response to completion —blockLast(),takeLast(1), or a WebFlux controller awaiting theMono<Msg>— ended up waiting for the full LLM-based consolidation call insideconsolidateMemory()(consolidator.consolidate(rc).block()), inflating agent-call latency by up to ~30s in the reported case. This also contradicted the harness memory documentation, which describes this step as running in the "background" in a "fire-and-forget" fashion.Root cause:
doOnComplete(...)(no scheduler switch) was the original implementation and matches what the docs still describe. A later, unrelated commit swapped it for.concatWith(Mono.fromRunnable(...).subscribeOn(Schedulers.boundedElastic())...), which moved the blocking work onto a different thread pool but did not actually detach it from the response —concatWithkeeps the maintenance Mono part of the same Flux the caller is awaiting.Changes:
MemoryMaintenanceMiddleware.onAgent: replaced.concatWith(...)with.doOnComplete(() -> ...).subscribe()so the maintenance Mono is genuinely detached — subscribed independently of the returned Flux, matching the fire-and-forget pattern already used elsewhere in this package (AsyncToolMiddleware,SubagentsMiddleware). The returned Flux now completes as soon as the underlying agent call does, regardless of how long maintenance takes. Error-handling semantics (onErrorResume+log.warn) are unchanged, only relocated.MemoryConsolidator.consolidate(): added a.timeout(CONSOLIDATION_TIMEOUT)on the model call (reusingExecutionConfig.MODEL_DEFAULTS's 5-minute timeout) so a hung model provider can no longer tie up a sharedboundedElasticworker thread indefinitely — this pool is shared process-wide with 40+ other call sites.MemoryMaintenanceMiddlewareto describe the actual (now-correct) detached behavior instead of the staleconcatWithreference.Why no doc changes: This fix restores the exact behavior the existing harness docs (
docs/v2/*/docs/harness/memory.md,compaction.md,workspace.md) already describe (background/fire-and-forget maintenance) — so those docs are accurate again as a result of this fix, rather than needing separate correction.How to test:
mvn test -pl agentscope-harness -am -Dtest=MemoryMaintenanceMiddlewareAsyncBehaviorTest,MemoryConsolidatorTimeoutTestMemoryMaintenanceMiddlewareAsyncBehaviorTest.onAgent_completesBeforeSlowConsolidationFinishes: mocks a slowMemoryConsolidator.consolidate()call and assertsonAgent(...)'s returned Flux completes in well under the consolidation duration — this test fails against the pre-fixconcatWithcode (verified locally: takes 3s instead of completing immediately).MemoryMaintenanceMiddlewareAsyncBehaviorTest.onAgent_onErrorResume_handlesExceptionFromDetachedMaintenance: verifies the detached maintenance error path is still handled byonErrorResume(usingHooks.onErrorDroppedto detect if it would otherwise be silently dropped).MemoryConsolidatorTimeoutTest.consolidate_timesOutInsteadOfHangingForever: usesStepVerifier.withVirtualTimeto confirm a model that never responds causesconsolidate()to error with aTimeoutExceptioninstead of hanging — verified this test fails to even compile without the fix (confirming it's tied to the change).Full harness module suite: 637/637 tests passing, 0 failures/errors. Every changed/added line has full line and branch coverage (verified via JaCoCo — no partial coverage on any touched line).
Fixes #2225
Checklist
mvn spotless:applymvn test)