fix(memory): rebuild agent on workspace file change so memory edits apply

This commit is contained in:
matevip 2026-05-29 16:05:21 +08:00
parent a27084f9cf
commit ceb4da642b
4 changed files with 52 additions and 1 deletions

View File

@ -215,6 +215,20 @@ public class AgentService {
agentInstances.remove(agentId);
}
/**
* Invalidate the cached agent instance whenever one of its workspace files
* changes. The system prompt (which embeds MEMORY.md / PROFILE.md / structured
* memory) is baked into the cached instance at build time, so memory edits made
* via tools, consolidation, or cleanup would otherwise stay invisible until an
* agent config change or restart. Rebuilding on the next turn picks them up.
*/
@org.springframework.context.event.EventListener
public void onWorkspaceFileChanged(vip.mate.workspace.document.event.WorkspaceFileChangedEvent event) {
if (event.agentId() != null) {
agentInstances.remove(event.agentId());
}
}
// ==================== 运行时入口 ====================
public String chat(Long agentId, String message, String conversationId) {

View File

@ -3,8 +3,10 @@ package vip.mate.workspace.document;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import vip.mate.workspace.document.event.WorkspaceFileChangedEvent;
import vip.mate.workspace.document.model.WorkspaceFileEntity;
import vip.mate.workspace.document.repository.WorkspaceFileMapper;
@ -31,6 +33,7 @@ import java.util.stream.Collectors;
public class WorkspaceFileService {
private final WorkspaceFileMapper fileMapper;
private final ApplicationEventPublisher eventPublisher;
/**
* 列出 Agent 的所有工作区文件按排序 + 文件名排列
@ -68,6 +71,7 @@ public class WorkspaceFileService {
existing.setContent(content);
existing.setFileSize(size);
fileMapper.updateById(existing);
eventPublisher.publishEvent(new WorkspaceFileChangedEvent(agentId, filename));
return existing;
} else {
WorkspaceFileEntity entity = new WorkspaceFileEntity();
@ -78,6 +82,7 @@ public class WorkspaceFileService {
entity.setEnabled(false);
entity.setSortOrder(0);
fileMapper.insert(entity);
eventPublisher.publishEvent(new WorkspaceFileChangedEvent(agentId, filename));
return entity;
}
}
@ -91,6 +96,7 @@ public class WorkspaceFileService {
new LambdaQueryWrapper<WorkspaceFileEntity>()
.eq(WorkspaceFileEntity::getAgentId, agentId)
.eq(WorkspaceFileEntity::getFilename, filename));
eventPublisher.publishEvent(new WorkspaceFileChangedEvent(agentId, filename));
}
/**

View File

@ -0,0 +1,16 @@
package vip.mate.workspace.document.event;
/**
* Published whenever an agent's workspace file is created, updated, or deleted.
* <p>
* Workspace files (AGENTS.md, SOUL.md, PROFILE.md, MEMORY.md, structured/*.md)
* are baked into the agent's system prompt when its runtime instance is built.
* Listeners use this to invalidate the cached agent instance so memory edits
* (tool writes, consolidation, cleanup) take effect on the next turn instead of
* only after an agent config change or restart.
*
* @param agentId the affected agent
* @param filename the workspace file that changed
*/
public record WorkspaceFileChangedEvent(Long agentId, String filename) {
}

View File

@ -38,6 +38,7 @@ import static org.mockito.Mockito.when;
class WorkspaceMemorySearchTest {
@Mock private WorkspaceFileMapper fileMapper;
@Mock private org.springframework.context.ApplicationEventPublisher eventPublisher;
private WorkspaceFileService service;
@BeforeAll
@ -51,7 +52,21 @@ class WorkspaceMemorySearchTest {
@BeforeEach
void setUp() {
service = new WorkspaceFileService(fileMapper);
service = new WorkspaceFileService(fileMapper, eventPublisher);
}
@Test
@DisplayName("saveFile publishes a change event so the cached agent instance is invalidated")
void saveFilePublishesChangeEvent() {
when(fileMapper.selectOne(any())).thenReturn(null); // new file path
service.saveFile(1000000001L, "MEMORY.md", "## 稳定事实\n- 用户语言:简体中文");
ArgumentCaptor<vip.mate.workspace.document.event.WorkspaceFileChangedEvent> captor =
ArgumentCaptor.forClass(vip.mate.workspace.document.event.WorkspaceFileChangedEvent.class);
org.mockito.Mockito.verify(eventPublisher).publishEvent(captor.capture());
assertThat(captor.getValue().agentId()).isEqualTo(1000000001L);
assertThat(captor.getValue().filename()).isEqualTo("MEMORY.md");
}
// ---------- tokenize ----------