diff --git a/mateclaw-server/src/main/java/vip/mate/goal/config/GoalProperties.java b/mateclaw-server/src/main/java/vip/mate/goal/config/GoalProperties.java index 7678f9d5..34ddb0c6 100644 --- a/mateclaw-server/src/main/java/vip/mate/goal/config/GoalProperties.java +++ b/mateclaw-server/src/main/java/vip/mate/goal/config/GoalProperties.java @@ -17,8 +17,14 @@ import org.springframework.stereotype.Component; @ConfigurationProperties(prefix = "mateclaw.goal") public class GoalProperties { - /** Master switch — when off, the graph never invokes GoalEvaluationNode. */ - private boolean enabled = false; + /** + * Master switch — when off, the graph never invokes GoalEvaluationNode + * (the conditional edge sees no active goal, so the node is unreachable). + * Defaults to true now that the full PR1-5 chain is in place; operators + * who want to disable goal evaluation can override via + * {@code mateclaw.goal.enabled=false} in application.yml. + */ + private boolean enabled = true; /** Default turn budget when the user doesn't override. */ private int defaultTurnBudget = 20; diff --git a/mateclaw-server/src/main/java/vip/mate/goal/service/GoalServiceImpl.java b/mateclaw-server/src/main/java/vip/mate/goal/service/GoalServiceImpl.java index d22161af..7f3e0468 100644 --- a/mateclaw-server/src/main/java/vip/mate/goal/service/GoalServiceImpl.java +++ b/mateclaw-server/src/main/java/vip/mate/goal/service/GoalServiceImpl.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -42,7 +43,6 @@ import java.util.function.IntSupplier; */ @Slf4j @Service -@RequiredArgsConstructor public class GoalServiceImpl implements GoalService { private static final int OPTIMISTIC_LOCK_MAX_RETRIES = 3; @@ -53,6 +53,32 @@ public class GoalServiceImpl implements GoalService { private final AuditEventService auditEventService; private final ObjectMapper objectMapper; + /** + * Optional — only set when the memory subsystem is wired. On goal + * completion we forward a synthetic "[goal completed] ..." turn so + * the memory dreaming pass can fold the outcome into long-term + * memory and the user can ask about it later. Failures are best + * effort: memory should never block the state-machine write. + */ + private vip.mate.memory.spi.MemoryManager memoryManager; + + public GoalServiceImpl(GoalMapper goalMapper, + GoalEventMapper eventMapper, + GoalProperties properties, + AuditEventService auditEventService, + ObjectMapper objectMapper) { + this.goalMapper = goalMapper; + this.eventMapper = eventMapper; + this.properties = properties; + this.auditEventService = auditEventService; + this.objectMapper = objectMapper; + } + + @Autowired(required = false) + public void setMemoryManager(vip.mate.memory.spi.MemoryManager memoryManager) { + this.memoryManager = memoryManager; + } + // ==================== CRUD ==================== @Override @@ -256,6 +282,24 @@ public class GoalServiceImpl implements GoalService { detail.put("evalLlmCallsUsed", g.getEvalLlmCallsUsed()); writeEvent(id, GoalEventType.COMPLETED, null, detail); recordAudit("goal.completed", g, detail); + + // RFC 48 PR5 — forward to long-term memory. Best-effort: a failing + // memory pipeline must not roll back the DB transition. + if (memoryManager != null) { + try { + String summary = g.getProgressSummary() != null && !g.getProgressSummary().isBlank() + ? g.getProgressSummary() + : "Final score: " + (result != null ? result.score() : "—"); + memoryManager.syncAll( + g.getAgentId(), + g.getConversationId(), + "[goal completed] " + g.getTitle(), + summary); + } catch (Exception e) { + log.debug("[GoalService] memory syncAll on goal completion failed: {}", e.getMessage()); + } + } + return goalMapper.selectById(id); }