diff --git a/agentscope-harness/src/main/java/io/agentscope/harness/agent/HarnessAgentBuilderSupport.java b/agentscope-harness/src/main/java/io/agentscope/harness/agent/HarnessAgentBuilderSupport.java index b688bec510..12c0fccc1f 100644 --- a/agentscope-harness/src/main/java/io/agentscope/harness/agent/HarnessAgentBuilderSupport.java +++ b/agentscope-harness/src/main/java/io/agentscope/harness/agent/HarnessAgentBuilderSupport.java @@ -36,6 +36,7 @@ import io.agentscope.harness.agent.filesystem.remote.store.NamespaceFactory; import io.agentscope.harness.agent.filesystem.sandbox.SandboxBackedFilesystem; import io.agentscope.harness.agent.filesystem.spec.LocalFilesystemSpec; +import io.agentscope.harness.agent.memory.MemoryConfig; import io.agentscope.harness.agent.memory.compaction.CompactionConfig; import io.agentscope.harness.agent.memory.compaction.ToolResultEvictionConfig; import io.agentscope.harness.agent.middleware.DynamicSubagentsMiddleware; @@ -311,6 +312,7 @@ static SubagentFactory buildGeneralPurposeFactory( final boolean capturedAgentTracingLogEnabled = b.agentTracingLogEnabled; final List capturedAdditionalContextFiles = List.copyOf(b.additionalContextFiles); final int capturedMaxContextTokens = b.maxContextTokens; + final MemoryConfig capturedMemoryConfig = b.memoryConfig; // Propagate the parent's (distributed) state store so an exposed subagent can be // re-materialized on another node / after a restart and still load its conversation // history by sessionId. Null in purely local default deployments — children then keep @@ -356,6 +358,7 @@ static SubagentFactory buildGeneralPurposeFactory( if (capturedModelExec != null) sub.modelExecutionConfig(capturedModelExec); if (capturedToolExec != null) sub.toolExecutionConfig(capturedToolExec); if (capturedGenOpts != null) sub.generateOptions(capturedGenOpts); + sub.memory(capturedMemoryConfig); if (capturedDisableCompaction) { sub.disableCompaction(); } else if (capturedCompactionConfig != null) { diff --git a/agentscope-harness/src/test/java/io/agentscope/harness/agent/HarnessAgentTest.java b/agentscope-harness/src/test/java/io/agentscope/harness/agent/HarnessAgentTest.java index aeed55da73..69ea8020af 100644 --- a/agentscope-harness/src/test/java/io/agentscope/harness/agent/HarnessAgentTest.java +++ b/agentscope-harness/src/test/java/io/agentscope/harness/agent/HarnessAgentTest.java @@ -54,6 +54,7 @@ import io.agentscope.harness.agent.filesystem.local.LocalFilesystem; import io.agentscope.harness.agent.filesystem.remote.store.InMemoryStore; import io.agentscope.harness.agent.filesystem.spec.RemoteFilesystemSpec; +import io.agentscope.harness.agent.memory.MemoryConfig; import io.agentscope.harness.agent.memory.compaction.CompactionConfig; import io.agentscope.harness.agent.middleware.AgentTraceMiddleware; import io.agentscope.harness.agent.middleware.SubagentEntry; @@ -1181,6 +1182,76 @@ void generalPurpose_mirrorCompactionConfig() throws Exception { assertNotNull(child.getCompactionHook(), "CompactionHook should be mirrored to GP child"); } + @Test + void generalPurpose_inheritsMemoryConfig_fromParent() throws Exception { + Files.createDirectories(workspace); + + Model parentModel = stubModel("ok"); + Model memoryModel = mock(Model.class); + when(memoryModel.getModelName()).thenReturn("memory-model"); + ChatResponse memoryChunk = + new ChatResponse( + "memory-id", + List.of(TextBlock.builder().text("memory-done").build()), + null, + Map.of(), + "stop"); + when(memoryModel.stream(anyList(), anyList(), any())).thenReturn(Flux.just(memoryChunk)); + + MemoryConfig memoryConfig = + MemoryConfig.builder() + .model(memoryModel) + .consolidationMinGap(Duration.ofMinutes(1)) + .build(); + + List entries = + HarnessAgent.builder() + .model(parentModel) + .workspace(workspace) + .memory(memoryConfig) + .buildSubagentEntries(workspace); + + HarnessAgent child = + (HarnessAgent) + entries.stream() + .filter(e -> "general-purpose".equals(e.name())) + .findFirst() + .orElseThrow() + .factory() + .create(RuntimeContext.empty()); + + child.call(userText("hi, keep this in memory"), RuntimeContext.empty()).block(); + + verify(memoryModel).stream(any(), any(), any()); + verify(parentModel, atLeast(1)).stream(any(), any(), any()); + } + + @Test + void generalPurpose_withoutMemoryConfig_usesParentModelOnly() throws Exception { + Files.createDirectories(workspace); + + Model parentModel = stubModel("ok"); + + List entries = + HarnessAgent.builder() + .model(parentModel) + .workspace(workspace) + .buildSubagentEntries(workspace); + + HarnessAgent child = + (HarnessAgent) + entries.stream() + .filter(e -> "general-purpose".equals(e.name())) + .findFirst() + .orElseThrow() + .factory() + .create(RuntimeContext.empty()); + + child.call(userText("hi, no memory config"), RuntimeContext.empty()).block(); + + verify(parentModel, atLeast(1)).stream(any(), any(), any()); + } + // ========================================================================= // Multiple declarations → same definition workspace // =========================================================================