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:
@ -17,6 +17,8 @@ use application::{
|
||||
UpdateAgentContextInput,
|
||||
AssignSkillToAgentInput, CreateSkillInput, DeleteSkillInput, ListSkillsInput,
|
||||
UnassignSkillFromAgentInput, UpdateSkillInput,
|
||||
CreateMemoryInput, DeleteMemoryInput, GetMemoryInput, ListMemoriesInput, ReadMemoryIndexInput,
|
||||
RecallMemoryInput, ResolveMemoryLinksInput, UpdateMemoryInput,
|
||||
};
|
||||
use domain::ports::PtyHandle;
|
||||
|
||||
@ -37,6 +39,8 @@ use crate::dto::{
|
||||
TemplateListDto, TerminalClosedDto, TerminalSessionDto, UpdateAgentContextRequestDto,
|
||||
UpdateTemplateRequestDto, WriteTerminalRequestDto, parse_skill_id, AssignSkillRequestDto,
|
||||
CreateSkillRequestDto, SkillDto, SkillListDto, UnassignSkillRequestDto, UpdateSkillRequestDto,
|
||||
parse_memory_slug, CreateMemoryRequestDto, MemoryDto, MemoryIndexDto, MemoryLinksDto,
|
||||
MemoryListDto, RecallMemoryRequestDto, UpdateMemoryRequestDto,
|
||||
};
|
||||
use domain::{SkillRef, SkillScope};
|
||||
use crate::pty::{PtyBridge, PtyChunk};
|
||||
@ -1392,3 +1396,197 @@ pub async fn unassign_skill_from_agent(
|
||||
.await
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Memory (LOT A — §14.5.1)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// `create_memory` — create a memory note in the project's store.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] (`INVALID` for an invalid slug/empty fields or
|
||||
/// malformed project id, `NOT_FOUND` if the project is unknown, `STORE` on
|
||||
/// failure).
|
||||
#[tauri::command]
|
||||
pub async fn create_memory(
|
||||
request: CreateMemoryRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<MemoryDto, ErrorDto> {
|
||||
let project = resolve_project(&request.project_id, &state).await?;
|
||||
state
|
||||
.create_memory
|
||||
.execute(CreateMemoryInput {
|
||||
project_root: project.root,
|
||||
name: request.name,
|
||||
description: request.description,
|
||||
r#type: request.r#type,
|
||||
content: request.content,
|
||||
})
|
||||
.await
|
||||
.map(MemoryDto::from)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `update_memory` — replace an existing memory note (re-validates invariants).
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] (`INVALID` for an invalid slug/empty fields,
|
||||
/// `NOT_FOUND` if the project is unknown, `STORE` on failure).
|
||||
#[tauri::command]
|
||||
pub async fn update_memory(
|
||||
request: UpdateMemoryRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<MemoryDto, ErrorDto> {
|
||||
let project = resolve_project(&request.project_id, &state).await?;
|
||||
let slug = parse_memory_slug(&request.slug)?;
|
||||
state
|
||||
.update_memory
|
||||
.execute(UpdateMemoryInput {
|
||||
project_root: project.root,
|
||||
slug,
|
||||
description: request.description,
|
||||
r#type: request.r#type,
|
||||
content: request.content,
|
||||
})
|
||||
.await
|
||||
.map(MemoryDto::from)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `list_memories` — list the project's memory notes.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] (`INVALID` for a malformed id, `NOT_FOUND` if the
|
||||
/// project is unknown, `STORE` on failure).
|
||||
#[tauri::command]
|
||||
pub async fn list_memories(
|
||||
project_id: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<MemoryListDto, ErrorDto> {
|
||||
let project = resolve_project(&project_id, &state).await?;
|
||||
state
|
||||
.list_memories
|
||||
.execute(ListMemoriesInput {
|
||||
project_root: project.root,
|
||||
})
|
||||
.await
|
||||
.map(MemoryListDto::from)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `get_memory` — read one memory note by slug.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] (`INVALID` for an invalid slug/id, `NOT_FOUND` if the
|
||||
/// project or note is unknown, `STORE` on failure).
|
||||
#[tauri::command]
|
||||
pub async fn get_memory(
|
||||
project_id: String,
|
||||
slug: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<MemoryDto, ErrorDto> {
|
||||
let project = resolve_project(&project_id, &state).await?;
|
||||
let slug = parse_memory_slug(&slug)?;
|
||||
state
|
||||
.get_memory
|
||||
.execute(GetMemoryInput {
|
||||
project_root: project.root,
|
||||
slug,
|
||||
})
|
||||
.await
|
||||
.map(MemoryDto::from)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `delete_memory` — remove a memory note (and its index row).
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] (`INVALID` for an invalid slug/id, `NOT_FOUND` if the
|
||||
/// project or note is unknown, `STORE` on failure).
|
||||
#[tauri::command]
|
||||
pub async fn delete_memory(
|
||||
project_id: String,
|
||||
slug: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<(), ErrorDto> {
|
||||
let project = resolve_project(&project_id, &state).await?;
|
||||
let slug = parse_memory_slug(&slug)?;
|
||||
state
|
||||
.delete_memory
|
||||
.execute(DeleteMemoryInput {
|
||||
project_root: project.root,
|
||||
slug,
|
||||
})
|
||||
.await
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `read_memory_index` — read the structured `MEMORY.md` index.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] (`INVALID` for a malformed id, `NOT_FOUND` if the
|
||||
/// project is unknown, `STORE` on failure).
|
||||
#[tauri::command]
|
||||
pub async fn read_memory_index(
|
||||
project_id: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<MemoryIndexDto, ErrorDto> {
|
||||
let project = resolve_project(&project_id, &state).await?;
|
||||
state
|
||||
.read_memory_index
|
||||
.execute(ReadMemoryIndexInput {
|
||||
project_root: project.root,
|
||||
})
|
||||
.await
|
||||
.map(MemoryIndexDto::from)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `recall_memory` — recall the most relevant memory entries for a query within
|
||||
/// a token budget (LOT B — §14.5.2).
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] (`INVALID` for a malformed id, `NOT_FOUND` if the
|
||||
/// project is unknown, `STORE` on failure). An empty or absent memory and a zero
|
||||
/// budget both yield an empty list, not an error.
|
||||
#[tauri::command]
|
||||
pub async fn recall_memory(
|
||||
request: RecallMemoryRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<MemoryIndexDto, ErrorDto> {
|
||||
let project = resolve_project(&request.project_id, &state).await?;
|
||||
state
|
||||
.recall_memory
|
||||
.execute(RecallMemoryInput {
|
||||
project_root: project.root,
|
||||
text: request.text,
|
||||
token_budget: request.token_budget,
|
||||
})
|
||||
.await
|
||||
.map(MemoryIndexDto::from)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `resolve_memory_links` — resolve a note's outgoing `[[slug]]` links.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] (`INVALID` for an invalid slug/id, `NOT_FOUND` if the
|
||||
/// project or source note is unknown, `STORE` on failure).
|
||||
#[tauri::command]
|
||||
pub async fn resolve_memory_links(
|
||||
project_id: String,
|
||||
slug: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<MemoryLinksDto, ErrorDto> {
|
||||
let project = resolve_project(&project_id, &state).await?;
|
||||
let slug = parse_memory_slug(&slug)?;
|
||||
state
|
||||
.resolve_memory_links
|
||||
.execute(ResolveMemoryLinksInput {
|
||||
project_root: project.root,
|
||||
slug,
|
||||
})
|
||||
.await
|
||||
.map(MemoryLinksDto::from)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
@ -1514,3 +1514,184 @@ pub fn parse_skill_id(raw: &str) -> Result<SkillId, ErrorDto> {
|
||||
message: format!("invalid skill id: {raw}"),
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Memory (LOT A — §14.5.1)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
use application::{
|
||||
CreateMemoryOutput, GetMemoryOutput, ListMemoriesOutput, ReadMemoryIndexOutput,
|
||||
RecallMemoryOutput, ResolveMemoryLinksOutput, UpdateMemoryOutput,
|
||||
};
|
||||
use domain::{Memory, MemoryIndexEntry, MemorySlug, MemoryType};
|
||||
|
||||
/// A memory note crossing the wire.
|
||||
///
|
||||
/// Built explicitly from [`Memory`] (its `body` is [`domain::MarkdownDoc`], not
|
||||
/// directly a JSON string) so the TS mirror gets a flat camelCase shape.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MemoryDto {
|
||||
/// The note's slug (its identity, also the file stem).
|
||||
pub name: String,
|
||||
/// One-line description (the index hook).
|
||||
pub description: String,
|
||||
/// The note's kind.
|
||||
pub r#type: MemoryType,
|
||||
/// Markdown body of the note.
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
impl From<Memory> for MemoryDto {
|
||||
fn from(memory: Memory) -> Self {
|
||||
Self {
|
||||
name: memory.frontmatter.name.as_str().to_owned(),
|
||||
description: memory.frontmatter.description,
|
||||
r#type: memory.frontmatter.r#type,
|
||||
content: memory.body.into_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CreateMemoryOutput> for MemoryDto {
|
||||
fn from(out: CreateMemoryOutput) -> Self {
|
||||
Self::from(out.memory)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UpdateMemoryOutput> for MemoryDto {
|
||||
fn from(out: UpdateMemoryOutput) -> Self {
|
||||
Self::from(out.memory)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GetMemoryOutput> for MemoryDto {
|
||||
fn from(out: GetMemoryOutput) -> Self {
|
||||
Self::from(out.memory)
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of memory notes (transparent array on the wire).
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct MemoryListDto(pub Vec<MemoryDto>);
|
||||
|
||||
impl From<ListMemoriesOutput> for MemoryListDto {
|
||||
fn from(out: ListMemoriesOutput) -> Self {
|
||||
Self(out.memories.into_iter().map(MemoryDto::from).collect())
|
||||
}
|
||||
}
|
||||
|
||||
/// One row of the structured `MEMORY.md` index crossing the wire.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MemoryIndexEntryDto {
|
||||
/// The note's slug.
|
||||
pub slug: String,
|
||||
/// The note's display title.
|
||||
pub title: String,
|
||||
/// The one-line hook (the frontmatter description).
|
||||
pub hook: String,
|
||||
/// The note's kind.
|
||||
pub r#type: MemoryType,
|
||||
}
|
||||
|
||||
impl From<MemoryIndexEntry> for MemoryIndexEntryDto {
|
||||
fn from(entry: MemoryIndexEntry) -> Self {
|
||||
Self {
|
||||
slug: entry.slug.as_str().to_owned(),
|
||||
title: entry.title,
|
||||
hook: entry.hook,
|
||||
r#type: entry.r#type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The structured memory index (transparent array on the wire).
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct MemoryIndexDto(pub Vec<MemoryIndexEntryDto>);
|
||||
|
||||
impl From<ReadMemoryIndexOutput> for MemoryIndexDto {
|
||||
fn from(out: ReadMemoryIndexOutput) -> Self {
|
||||
Self(out.entries.into_iter().map(MemoryIndexEntryDto::from).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RecallMemoryOutput> for MemoryIndexDto {
|
||||
fn from(out: RecallMemoryOutput) -> Self {
|
||||
Self(out.entries.into_iter().map(MemoryIndexEntryDto::from).collect())
|
||||
}
|
||||
}
|
||||
|
||||
/// A note's resolved outgoing links — the target slugs (transparent array).
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct MemoryLinksDto(pub Vec<String>);
|
||||
|
||||
impl From<ResolveMemoryLinksOutput> for MemoryLinksDto {
|
||||
fn from(out: ResolveMemoryLinksOutput) -> Self {
|
||||
Self(
|
||||
out.links
|
||||
.into_iter()
|
||||
.map(|link| link.target.as_str().to_owned())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Request DTO for `create_memory`.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreateMemoryRequestDto {
|
||||
/// Owning project (resolved to a root).
|
||||
pub project_id: String,
|
||||
/// Raw slug for the new note.
|
||||
pub name: String,
|
||||
/// One-line description (the index hook).
|
||||
pub description: String,
|
||||
/// The note's kind.
|
||||
pub r#type: MemoryType,
|
||||
/// Markdown body of the note.
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
/// Request DTO for `update_memory`.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdateMemoryRequestDto {
|
||||
/// Owning project (resolved to a root).
|
||||
pub project_id: String,
|
||||
/// Slug of the note to replace.
|
||||
pub slug: String,
|
||||
/// New description (the index hook).
|
||||
pub description: String,
|
||||
/// New kind.
|
||||
pub r#type: MemoryType,
|
||||
/// New Markdown body.
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
/// Request DTO for `recall_memory` (LOT B — §14.5.2).
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RecallMemoryRequestDto {
|
||||
/// Owning project (resolved to a root).
|
||||
pub project_id: String,
|
||||
/// The recall query (often the agent's current working context).
|
||||
pub text: String,
|
||||
/// Approximate token budget bounding the recalled entries (`0` ⇒ empty).
|
||||
pub token_budget: usize,
|
||||
}
|
||||
|
||||
/// Parses a memory slug string coming from the frontend.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] with code `INVALID` if the string is not a valid
|
||||
/// kebab-case slug.
|
||||
pub fn parse_memory_slug(raw: &str) -> Result<MemorySlug, ErrorDto> {
|
||||
MemorySlug::new(raw).map_err(|_| ErrorDto {
|
||||
code: "INVALID".to_owned(),
|
||||
message: format!("invalid memory slug: {raw}"),
|
||||
})
|
||||
}
|
||||
|
||||
@ -111,6 +111,24 @@ pub enum DomainEventDto {
|
||||
/// Whether IdeA handled it successfully.
|
||||
ok: bool,
|
||||
},
|
||||
/// A memory note was created or updated.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MemorySaved {
|
||||
/// The saved note's slug.
|
||||
slug: String,
|
||||
},
|
||||
/// A memory note was deleted.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MemoryDeleted {
|
||||
/// The deleted note's slug.
|
||||
slug: String,
|
||||
},
|
||||
/// The aggregated `MEMORY.md` index was rebuilt.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MemoryIndexRebuilt {
|
||||
/// Project id.
|
||||
project_id: String,
|
||||
},
|
||||
/// Raw PTY output (normally routed to a per-session channel, not here).
|
||||
#[serde(rename_all = "camelCase")]
|
||||
PtyOutput {
|
||||
@ -185,6 +203,15 @@ impl From<&DomainEvent> for DomainEventDto {
|
||||
action: action.clone(),
|
||||
ok: *ok,
|
||||
},
|
||||
DomainEvent::MemorySaved { slug } => Self::MemorySaved {
|
||||
slug: slug.as_str().to_string(),
|
||||
},
|
||||
DomainEvent::MemoryDeleted { slug } => Self::MemoryDeleted {
|
||||
slug: slug.as_str().to_string(),
|
||||
},
|
||||
DomainEvent::MemoryIndexRebuilt { project_id } => Self::MemoryIndexRebuilt {
|
||||
project_id: project_id.to_string(),
|
||||
},
|
||||
DomainEvent::PtyOutput { session_id, bytes } => Self::PtyOutput {
|
||||
session_id: session_id.to_string(),
|
||||
bytes: bytes.clone(),
|
||||
|
||||
@ -140,6 +140,14 @@ pub fn run() {
|
||||
commands::delete_skill,
|
||||
commands::assign_skill_to_agent,
|
||||
commands::unassign_skill_from_agent,
|
||||
commands::create_memory,
|
||||
commands::update_memory,
|
||||
commands::list_memories,
|
||||
commands::get_memory,
|
||||
commands::delete_memory,
|
||||
commands::read_memory_index,
|
||||
commands::recall_memory,
|
||||
commands::resolve_memory_links,
|
||||
commands::move_tab_to_new_window,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
|
||||
@ -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,
|
||||
|
||||
Reference in New Issue
Block a user