diff --git a/agentscope-harness/src/main/java/io/agentscope/harness/agent/HarnessAgent.java b/agentscope-harness/src/main/java/io/agentscope/harness/agent/HarnessAgent.java index bf31afe8db..4cc0ec2e09 100644 --- a/agentscope-harness/src/main/java/io/agentscope/harness/agent/HarnessAgent.java +++ b/agentscope-harness/src/main/java/io/agentscope/harness/agent/HarnessAgent.java @@ -2439,7 +2439,8 @@ public HarnessAgent build() { effectiveFlushPrompt, memoryConfig.flushTrigger(), effectiveIsolationScope, - periodicGate)); + periodicGate, + memoryConfig.asyncFlush())); String effectiveConsolidationPrompt = memoryConfig.consolidationPrompt() != null diff --git a/agentscope-harness/src/main/java/io/agentscope/harness/agent/memory/MemoryConfig.java b/agentscope-harness/src/main/java/io/agentscope/harness/agent/memory/MemoryConfig.java index dad15e6cb3..f931a2caad 100644 --- a/agentscope-harness/src/main/java/io/agentscope/harness/agent/memory/MemoryConfig.java +++ b/agentscope-harness/src/main/java/io/agentscope/harness/agent/memory/MemoryConfig.java @@ -32,7 +32,7 @@ *
  • Flush — extracts long-term memories from a conversation window into today's * daily ledger ({@code memory/YYYY-MM-DD.md}). Prompt: {@link #flushPrompt()}, * defaults to {@link MemoryFlushManager#DEFAULT_FLUSH_PROMPT}. Trigger: - * {@link #flushTrigger()}.
  • + * {@link #flushTrigger()}. Completion mode: {@link #asyncFlush()}. *
  • Consolidation — periodically merges daily ledgers into the curated * {@code MEMORY.md}. Prompt: {@link #consolidationPrompt()}, defaults to * {@link MemoryConsolidator#DEFAULT_CONSOLIDATION_PROMPT}. Run cadence: @@ -147,6 +147,7 @@ public String toString() { private final int dailyFileRetentionDays; private final int sessionRetentionDays; private final FlushTrigger flushTrigger; + private final boolean asyncFlush; private MemoryConfig(Builder b) { this.model = b.model; @@ -157,6 +158,7 @@ private MemoryConfig(Builder b) { this.dailyFileRetentionDays = b.dailyFileRetentionDays; this.sessionRetentionDays = b.sessionRetentionDays; this.flushTrigger = b.flushTrigger; + this.asyncFlush = b.asyncFlush; } /** @@ -206,6 +208,14 @@ public FlushTrigger flushTrigger() { return flushTrigger; } + /** + * Whether the per-call memory flush runs asynchronously after the response stream completes. + * Defaults to {@code false} so memory persistence retains its historical completion semantics. + */ + public boolean asyncFlush() { + return asyncFlush; + } + /** Returns a config equivalent to the harness's historical defaults. */ public static MemoryConfig defaults() { return new Builder().build(); @@ -225,6 +235,7 @@ public static final class Builder { private int dailyFileRetentionDays = DEFAULT_DAILY_FILE_RETENTION_DAYS; private int sessionRetentionDays = DEFAULT_SESSION_RETENTION_DAYS; private FlushTrigger flushTrigger = FlushTrigger.always(); + private boolean asyncFlush = false; /** * Sets a dedicated model for memory operations (flush + consolidation), @@ -331,6 +342,16 @@ public Builder flushTrigger(FlushTrigger flushTrigger) { return this; } + /** + * Sets whether the per-call memory flush should run asynchronously after the response + * stream completes. Background failures are logged and do not fail the completed response. + * Disabled by default so the response waits for persistence to finish. + */ + public Builder asyncFlush(boolean asyncFlush) { + this.asyncFlush = asyncFlush; + return this; + } + public MemoryConfig build() { return new MemoryConfig(this); } diff --git a/agentscope-harness/src/main/java/io/agentscope/harness/agent/middleware/MemoryFlushMiddleware.java b/agentscope-harness/src/main/java/io/agentscope/harness/agent/middleware/MemoryFlushMiddleware.java index 39c6f8c5cb..dd4de527d5 100644 --- a/agentscope-harness/src/main/java/io/agentscope/harness/agent/middleware/MemoryFlushMiddleware.java +++ b/agentscope-harness/src/main/java/io/agentscope/harness/agent/middleware/MemoryFlushMiddleware.java @@ -34,7 +34,9 @@ import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; +import reactor.util.context.ContextView; /** * Middleware that triggers memory flush and message offload at the end of each agent call. @@ -57,6 +59,13 @@ * which runs independently of memory flush so history stays complete even when flush is * disabled. * + *

    By default the response stream waits for the per-call flush to finish. When asynchronous + * flush is enabled, the middleware copies the completed call's messages and schedules a detached + * flush on a bounded single-worker scheduler. Failures are logged without changing the completed + * response. The scheduler accepts at most three queued tasks; excess flushes are rejected and + * logged rather than accumulating without bound. Detached tasks are not awaited during agent + * shutdown. + * *

    The throttle window is tracked per isolation key, which matches the memory data * isolation in use: *