diff --git a/mateclaw-server/src/main/java/vip/mate/memory/fact/provider/FactMemoryProvider.java b/mateclaw-server/src/main/java/vip/mate/memory/fact/provider/FactMemoryProvider.java
new file mode 100644
index 00000000..764fc352
--- /dev/null
+++ b/mateclaw-server/src/main/java/vip/mate/memory/fact/provider/FactMemoryProvider.java
@@ -0,0 +1,80 @@
+package vip.mate.memory.fact.provider;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import vip.mate.memory.MemoryProperties;
+import vip.mate.memory.fact.model.FactEntity;
+import vip.mate.memory.fact.projection.FactProjectionBuilder;
+import vip.mate.memory.fact.query.FactQueryService;
+import vip.mate.memory.fact.tool.FactQueryTool;
+import vip.mate.memory.spi.MemoryProvider;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Read-only SPI provider that surfaces fact projection in the memory lifecycle.
+ *
+ * - prefetch: returns relevant facts for the user query (injected into prompt)
+ * - syncTurn: bumps use_count for recalled facts
+ * - onMemoryWrite: triggers incremental projection rebuild
+ * - getToolBeans: exposes FactQueryTool to agents
+ *
+ * @author MateClaw Team
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class FactMemoryProvider implements MemoryProvider {
+
+ private final FactQueryService queryService;
+ private final FactProjectionBuilder projectionBuilder;
+ private final FactQueryTool factQueryTool;
+ private final MemoryProperties properties;
+
+ @Override
+ public String id() { return "fact_store"; }
+
+ @Override
+ public int order() { return 200; }
+
+ @Override
+ public boolean isAvailable() {
+ return properties.getFact().isProjectionEnabled();
+ }
+
+ @Override
+ public String prefetch(Long agentId, String userQuery) {
+ if (!properties.getFact().isProjectionEnabled()) return "";
+ List facts = queryService.recallRelevant(agentId, userQuery);
+ if (facts.isEmpty()) return "";
+
+ // Bump usage
+ queryService.bumpUseCount(facts.stream().map(FactEntity::getId).toList());
+
+ String block = facts.stream()
+ .map(f -> String.format("- %s %s %s", f.getSubject(), f.getPredicate(), f.getObjectValue()))
+ .collect(Collectors.joining("\n"));
+ return "\n" + block + "\n";
+ }
+
+ @Override
+ public void syncTurn(Long agentId, String conversationId, String userMessage, String assistantReply) {
+ // No-op — bumpUseCount already called in prefetch
+ }
+
+ @Override
+ public void onMemoryWrite(Long agentId, String target, String action, String content) {
+ if (!properties.getFact().isProjectionEnabled()) return;
+ // Incremental rebuild for the changed file
+ projectionBuilder.rebuildOne(agentId, target, content);
+ }
+
+ @Override
+ public List