feat(memory): système de mémoire projet model-agnostic (L14, LOT A+B+C)

Base de connaissance persistante par projet, indépendante de tout modèle/CLU
et de git. Cadrage archi en §14.5 (ARCHITECTURE.md), cycle Archi→Dev→Test.

LOT A — étage 1 (.md, source de vérité)
  - domaine: entité Memory (+ MemorySlug, MemoryType, MemoryFrontmatter,
    MemoryLink, MemoryIndexEntry), liens [[slug]], index MEMORY.md dérivé
  - port MemoryStore + MemoryError, adapter FsMemoryStore (.ideai/memory/)
  - application: 7 use cases (Create/Update/List/Get/Delete/ReadIndex/
    ResolveLinks), From<MemoryError> for AppError
  - app-tauri: commandes + DTO, events MemorySaved/MemoryDeleted
  - suppression de la variante morte DomainError::MalformedFrontmatter

LOT B — rappel adaptatif (étage 1)
  - port MemoryRecall + MemoryQuery, adapter NaiveMemoryRecall (troncature
    au budget de tokens, court-circuit budget-0), use case RecallMemory

LOT C — étage 2 vectoriel (structure complète, zéro dépendance lourde)
  - port Embedder + EmbedderError, profils déclaratifs EmbedderProfile/
    EmbedderStrategy (embedder.json)
  - VectorMemoryRecall (cosinus, cache .ideai/memory/.index/ gitignoré)
  - AdaptiveMemoryRecall (bascule pure should_use_vector), défaut none
  - HashEmbedder (déterministe, tests), StubEmbedder (onnx/server/api)

Tests: 57 binaires verts, build + clippy --workspace sans warning.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 08:47:23 +02:00
parent 3ed0f6b45f
commit 98a8b7292a
30 changed files with 4978 additions and 14 deletions

View File

@ -19,19 +19,22 @@ use application::{
OpenTerminal, OrchestratorService, ReadAgentContext, ReferenceProfiles, RenameLayout,
ResizeTerminal, SaveProfile, SetActiveLayout, SnapshotRunningAgents,
AssignSkillToAgent, CreateSkill, DeleteSkill, UnassignSkillFromAgent, UpdateSkill,
CreateMemory, DeleteMemory, GetMemory, ListMemories, ReadMemoryIndex, RecallMemory,
ResolveMemoryLinks, UpdateMemory,
SyncAgentWithTemplate, TerminalSessions, UpdateAgentContext, UpdateTemplate, WriteToTerminal,
};
use domain::ports::{
AgentContextStore, AgentRuntime, Clock, EventBus, FileSystem, GitPort, IdGenerator,
ProcessSpawner, ProfileStore, ProjectStore, PtyPort, SkillStore, TemplateStore,
MemoryRecall, MemoryStore, ProcessSpawner, ProfileStore, ProjectStore, PtyPort, SkillStore,
TemplateStore,
};
use domain::{DomainEvent, Project, ProjectId};
use infrastructure::{
ClaudeTranscriptInspector, CliAgentRuntime, FsOrchestratorWatcher, FsProfileStore,
FsProjectStore, FsSkillStore, FsTemplateStore, Git2Repository, IdeaiContextStore,
LocalFileSystem, LocalProcessSpawner, OrchestratorWatchHandle, PortablePtyAdapter, SystemClock,
TokioBroadcastEventBus, UuidGenerator,
FsMemoryStore, FsProjectStore, FsSkillStore, FsTemplateStore, Git2Repository, IdeaiContextStore,
LocalFileSystem, LocalProcessSpawner, NaiveMemoryRecall, OrchestratorWatchHandle,
PortablePtyAdapter, SystemClock, TokioBroadcastEventBus, UuidGenerator,
};
use crate::pty::PtyBridge;
@ -172,6 +175,25 @@ pub struct AppState {
pub assign_skill: Arc<AssignSkillToAgent>,
/// Unassign a skill from an agent.
pub unassign_skill: Arc<UnassignSkillFromAgent>,
// --- Memory (LOT A — §14.5.1) ---
/// Create a memory note in the project's store.
pub create_memory: Arc<CreateMemory>,
/// Replace an existing memory note.
pub update_memory: Arc<UpdateMemory>,
/// List the project's memory notes.
pub list_memories: Arc<ListMemories>,
/// Read one memory note by slug.
pub get_memory: Arc<GetMemory>,
/// Delete a memory note.
pub delete_memory: Arc<DeleteMemory>,
/// Read the structured `MEMORY.md` index.
pub read_memory_index: Arc<ReadMemoryIndex>,
/// Resolve a note's outgoing `[[slug]]` links.
pub resolve_memory_links: Arc<ResolveMemoryLinks>,
/// Recall the most relevant memory entries for a query within a budget
/// (LOT B — §14.5.2).
pub recall_memory: Arc<RecallMemory>,
// --- Orchestrator (§14.3) ---
/// Dispatches validated orchestrator requests to the agent/skill use cases.
/// Shared by every per-project filesystem watcher.
@ -458,6 +480,36 @@ impl AppState {
Arc::clone(&events_port),
));
// --- Memory store + use cases (LOT A — §14.5.1) ---
// A single FsMemoryStore takes the project root per call (like the skill
// store), so one instance serves every open project. The mutating use
// cases share the event bus to announce Memory{Saved,Deleted}.
let memory_store = Arc::new(FsMemoryStore::new(Arc::clone(&fs_port)));
let memory_store_port = Arc::clone(&memory_store) as Arc<dyn MemoryStore>;
let create_memory = Arc::new(CreateMemory::new(
Arc::clone(&memory_store_port),
Arc::clone(&events_port),
));
let update_memory = Arc::new(UpdateMemory::new(
Arc::clone(&memory_store_port),
Arc::clone(&events_port),
));
let list_memories = Arc::new(ListMemories::new(Arc::clone(&memory_store_port)));
let get_memory = Arc::new(GetMemory::new(Arc::clone(&memory_store_port)));
let delete_memory = Arc::new(DeleteMemory::new(
Arc::clone(&memory_store_port),
Arc::clone(&events_port),
));
let read_memory_index = Arc::new(ReadMemoryIndex::new(Arc::clone(&memory_store_port)));
let resolve_memory_links =
Arc::new(ResolveMemoryLinks::new(Arc::clone(&memory_store_port)));
// Naïve recall (LOT B): composes the same store, returns index entries in
// order, truncated to the token budget. The default, dependency-free
// MemoryRecall; substitutable by a VectorMemoryRecall (LOT C).
let memory_recall_port =
Arc::new(NaiveMemoryRecall::new(Arc::clone(&memory_store_port))) as Arc<dyn MemoryRecall>;
let recall_memory = Arc::new(RecallMemory::new(memory_recall_port));
// --- Orchestrator service (§14.3) ---
// Dispatches file-based orchestrator requests through the *same* use cases
// the UI drives (IdeA stays the single source of truth for the agent/skill
@ -540,6 +592,14 @@ impl AppState {
delete_skill,
assign_skill,
unassign_skill,
create_memory,
update_memory,
list_memories,
get_memory,
delete_memory,
read_memory_index,
resolve_memory_links,
recall_memory,
orchestrator_service,
orchestrator_watchers: Mutex::new(HashMap::new()),
move_tab,