pub struct MemoryService { /* private fields */ }Expand description
Wraps AppState and exposes one async method per logical operation.
Why: see module docs. Lets HTTP handlers stay thin and lets non-HTTP
callers (chat tool dispatch, RPC bridges) reuse the same code paths.
What: Clone (cheap — only the inner AppState is shared); construct
with MemoryService::new(state).
Test: every method is covered by the corresponding handler test in
web::tests.
Implementations§
Source§impl MemoryService
impl MemoryService
Sourcepub fn new(state: AppState) -> Self
pub fn new(state: AppState) -> Self
Construct a new service wrapper.
Why: handlers cheaply re-wrap their AppState on every request; the
cost is just an Arc clone, so we don’t bother caching the wrapper.
What: stores the AppState for later method calls.
Test: trivial — covered indirectly by every handler test.
Sourcepub fn state(&self) -> &AppState
pub fn state(&self) -> &AppState
Borrow the inner AppState.
Why: some handlers still need direct access (SSE broadcaster, session
store, etc.) while we incrementally extract code into the service.
What: returns a borrowed reference to the wrapped AppState.
Test: not directly tested; surface-level accessor.
Sourcepub async fn status(&self) -> StatusPayload
pub async fn status(&self) -> StatusPayload
Build the aggregate /api/v1/status payload.
Why: dashboard widgets and the MCP get_status tool need the same
roll-up; centralising avoids drift between the two surfaces.
What: walks every persisted palace, sums drawer/vector/triple counts,
and returns the StatusPayload.
Test: status_endpoint_returns_payload.
Sourcepub fn aggregate_status_event(&self) -> DaemonEvent
pub fn aggregate_status_event(&self) -> DaemonEvent
Compute the aggregate StatusChanged event used by SSE consumers.
Why: mutating handlers — and the periodic status ticker — push a
refreshed status snapshot so dashboards stay in sync without an
extra /api/v1/status request.
Why (issue #228): this used to call PalaceRegistry::list_palaces
(a synchronous disk walk) + open_palace (more disk I/O on first
call) for every palace on every emit. Since every persisted palace
is already loaded into the in-memory registry by
AppState::load_palaces_from_disk at startup (and every create_palace
keeps it in sync), iterating the in-memory registry returns the same
counts without touching disk.
What: iterates state.registry.list() (a DashMap snapshot) and
sums the live handle stats via [collect_palace_stats]. Returns a
DaemonEvent::StatusChanged. Palaces that fail to resolve in the
registry (race during shutdown) are silently skipped — the next
emit will catch them.
Test: indirectly via SSE integration tests; the math is identical to
the disk-walk implementation and the status_endpoint_returns_payload
test still passes against status() (which keeps the disk view for
the dedicated endpoint).
Sourcepub async fn list_palaces(&self) -> ServiceResult<Vec<PalaceInfo>>
pub async fn list_palaces(&self) -> ServiceResult<Vec<PalaceInfo>>
List every palace on disk, enriched with live handle stats.
Why: shared between the HTTP handler and the chat tool dispatcher;
both want the same PalaceInfo shape. Issue #185 added the
reserved-prefix filter so internal “system” palaces (e.g. the
__health_probe__ palace used by /health) never surface in the
admin UI, TUI, or any user-facing roster.
What: walks the registry, drops any palace whose id starts with the
reserved __ prefix, and builds a PalaceInfo per remaining row.
Test: palace_list_includes_richer_counts, palace_list_includes_graph_counts,
health_probe_palace_is_invisible (in web::tests).
Sourcepub async fn create_palace(
&self,
body: CreatePalaceBody,
source: ActivitySource,
) -> ServiceResult<String>
pub async fn create_palace( &self, body: CreatePalaceBody, source: ActivitySource, ) -> ServiceResult<String>
Create a new palace and emit the corresponding activity event.
Why: trims duplicated work between the HTTP handler and any future
non-HTTP creation flow.
What: validates the name, builds the Palace row, calls
PalaceRegistry::create_palace, and emits PalaceCreated. Returns
the new palace id.
Test: covered indirectly by palace_list_includes_richer_counts (which
posts a palace through the HTTP layer then reads it back).
Sourcepub async fn delete_palace(
&self,
palace_id: &str,
force: bool,
) -> ServiceResult<()>
pub async fn delete_palace( &self, palace_id: &str, force: bool, ) -> ServiceResult<()>
Delete a palace from disk, optionally rejecting non-empty palaces.
Why: Issue #180 — operators need a way to drop an entire palace
without going through drawer-by-drawer deletion. Defaulting to a
“must be empty” guard prevents fat-finger destruction of populated
palaces; force=true is the explicit opt-in to the destructive path.
What: 1) confirms the palace exists on disk (else NotFound),
2) when !force, lists drawers via the live handle and returns
BadRequest("Palace has drawers; pass force=true to delete") if
the palace is non-empty, 3) drops the in-memory registry entry so
future opens hit the (now-missing) disk state, 4) removes
<data_root>/<palace_id>/ recursively via tokio::fs::remove_dir_all,
and 5) emits an aggregate StatusChanged so dashboards refresh.
Test: delete_palace_removes_dir_when_empty,
delete_palace_refuses_when_drawers_present,
delete_palace_force_removes_populated_palace,
delete_palace_returns_not_found_for_missing_id in web::tests.
Sourcepub async fn update_palace_name(
&self,
palace_id: &str,
name: &str,
) -> Result<Value>
pub async fn update_palace_name( &self, palace_id: &str, name: &str, ) -> Result<Value>
Rename a palace’s display name without touching its data.
Why: Operators need to fix typos and rebrand palaces without dropping
the underlying drawers / vectors / KG. The palace id (the directory
name on disk) is immutable — only the human-readable name field in
palace.json changes — so cached PalaceHandles stay valid and no
registry invalidation is required.
What: 1) loads the palace via PalaceStore::load_palace (404 when the
directory or palace.json is missing), 2) trims the new name and
returns BadRequest when empty, 3) mutates palace.name and writes
the metadata back through the atomic PalaceStore::save_palace
(tmp file + rename), 4) emits an aggregate StatusChanged so
dashboards re-render the relabelled palace, 5) returns the updated
palace as JSON (enriched with the live handle stats, so callers see
drawer/vector/KG counts in the same shape as GET /palaces/{id}).
Test: update_palace_name_renames_palace,
update_palace_name_rejects_empty_name,
update_palace_name_returns_not_found_for_missing_id in web::tests.
Sourcepub async fn update_palace_name_typed(
&self,
palace_id: &str,
name: &str,
) -> ServiceResult<Value>
pub async fn update_palace_name_typed( &self, palace_id: &str, name: &str, ) -> ServiceResult<Value>
Typed variant of Self::update_palace_name used by the HTTP handler.
Why: HTTP needs to distinguish 400 (empty name) from 404 (missing
palace) so the right status code is emitted; the chat / MCP tool
only cares about a Result<Value> because both errors are surfaced
as opaque MCP error strings. Keeping a typed variant alongside the
untyped one keeps the wire shape correct on both surfaces without
asking either caller to parse error strings.
What: same as Self::update_palace_name but returns
ServiceError::BadRequest for empty names and
ServiceError::NotFound for missing palace metadata.
Test: update_palace_name_renames_palace,
update_palace_name_rejects_empty_name,
update_palace_name_returns_not_found_for_missing_id.
Sourcepub async fn get_palace(&self, id: &str) -> ServiceResult<PalaceInfo>
pub async fn get_palace(&self, id: &str) -> ServiceResult<PalaceInfo>
Look up a single palace by id and enrich with live handle stats.
Why: distinct 404 vs. 500 path is needed by both HTTP and chat callers.
What: returns NotFound when the id is unknown, otherwise a fully
populated PalaceInfo.
Test: indirectly via health_endpoint_round_trip_with_palace_is_ok.
Sourcepub async fn list_drawers(
&self,
id: &str,
q: ListDrawersQuery,
) -> ServiceResult<Value>
pub async fn list_drawers( &self, id: &str, q: ListDrawersQuery, ) -> ServiceResult<Value>
List drawers in a palace with optional room/tag filters and pagination.
Why: deduplicates the open-handle + listing path between HTTP and chat,
and (issue #184) lets the TUI activity panel page through drawers in
creation-date order without breaking the importance-sorted default the
legacy callers rely on.
What: opens the palace handle, fetches a window of drawers, optionally
re-sorts by created_at descending when sort = "created_desc"
(leaving the importance-desc default untouched), then drops the
leading offset rows and keeps limit. For created_desc the
window must cover the full filtered set (otherwise the importance
pre-sort hides truly-recent low-importance drawers), so the window
is widened to a sane ceiling (MAX_DRAWER_WINDOW); the default
importance path keeps a tight limit+offset window.
Returns the serialised JSON array.
Test: service::tests::list_drawers_creates_desc_paginates.
Sourcepub async fn create_drawer(
&self,
id: &str,
body: CreateDrawerBody,
creator: CreatorInfo,
source: ActivitySource,
) -> ServiceResult<Uuid>
pub async fn create_drawer( &self, id: &str, body: CreateDrawerBody, creator: CreatorInfo, source: ActivitySource, ) -> ServiceResult<Uuid>
Store a new drawer and emit the matching activity events.
Why: HTTP and chat both need the auto-KG-extraction follow-up; this
method keeps that side-effect chain in one place.
What: opens the palace, stores the drawer via PalaceHandle::remember,
emits DrawerAdded + StatusChanged, then triggers
tools::auto_extract_and_assert. Returns the new drawer id.
Test: http_create_drawer_runs_auto_kg_extraction.
Sourcepub async fn delete_drawer(
&self,
id: &str,
drawer_id: &str,
source: ActivitySource,
) -> ServiceResult<()>
pub async fn delete_drawer( &self, id: &str, drawer_id: &str, source: ActivitySource, ) -> ServiceResult<()>
Forget (delete) a drawer and emit the matching events.
Why: same dedup story as create_drawer.
What: parses the drawer UUID, calls PalaceHandle::forget, emits
DrawerDeleted + StatusChanged.
Test: indirectly via the drawer-related HTTP tests.
Sourcepub async fn recall(
&self,
id: &str,
query: &str,
top_k: usize,
deep: bool,
) -> ServiceResult<Value>
pub async fn recall( &self, id: &str, query: &str, top_k: usize, deep: bool, ) -> ServiceResult<Value>
Per-palace recall (semantic search), optionally with deep retrieval.
Why: HTTP and chat tools both perform the same fan-out logic.
What: opens the palace handle and dispatches to the shallow or deep
recall helper. Returns a JSON array of flattened drawer rows (the
recall_entry_json shape from issue #69).
Test: recall_entry_json_hoists_drawer_fields.
Sourcepub async fn recall_all(&self, query: &str, top_k: usize, deep: bool) -> Value
pub async fn recall_all(&self, query: &str, top_k: usize, deep: bool) -> Value
Cross-palace recall.
Why: shared between /api/v1/recall and the memory_recall_all chat
tool. Encapsulating the open-everything-fanout-merge dance avoids
drift.
What: lists every palace, opens handles (skipping failures with a
tracing::warn!), delegates to
recall_across_palaces_with_default_embedder. Returns a JSON array.
Test: indirectly via recall_across_palaces_merges_results and the
MCP memory_recall_all integration paths.
Sourcepub async fn kg_query(
&self,
id: &str,
subject: &str,
) -> ServiceResult<Vec<Triple>>
pub async fn kg_query( &self, id: &str, subject: &str, ) -> ServiceResult<Vec<Triple>>
Query the KG for all active triples whose subject matches.
Sourcepub async fn kg_assert(&self, id: &str, body: KgAssertBody) -> ServiceResult<()>
pub async fn kg_assert(&self, id: &str, body: KgAssertBody) -> ServiceResult<()>
Assert a triple in the KG.
Sourcepub async fn kg_retract_triple(
&self,
id: &str,
subject: &str,
predicate: &str,
) -> ServiceResult<bool>
pub async fn kg_retract_triple( &self, id: &str, subject: &str, predicate: &str, ) -> ServiceResult<bool>
Retract the single active triple identified by (subject, predicate).
Why: Issue #278 — the DELETE /kg/triples/<id> HTTP endpoint needs a
service-layer method so the HTTP handler stays a thin adapter.
What: Opens the palace handle, calls KnowledgeGraph::retract, and
maps the closed count to a 204/404 signal: Ok(true) when at least
one interval was closed, Ok(false) when no active triple matched.
Test: Covered by kg_delete_triple_returns_204_on_success in
web::tests.
Sourcepub async fn kg_list_subjects(
&self,
id: &str,
limit: usize,
) -> ServiceResult<Vec<String>>
pub async fn kg_list_subjects( &self, id: &str, limit: usize, ) -> ServiceResult<Vec<String>>
List distinct subjects in the KG.
Sourcepub async fn kg_list_subjects_with_counts(
&self,
id: &str,
limit: usize,
) -> ServiceResult<Vec<(String, u64)>>
pub async fn kg_list_subjects_with_counts( &self, id: &str, limit: usize, ) -> ServiceResult<Vec<(String, u64)>>
List distinct subjects in the KG paired with their active-triple count.
Sourcepub async fn kg_list_all(
&self,
id: &str,
limit: usize,
offset: usize,
) -> ServiceResult<Vec<Triple>>
pub async fn kg_list_all( &self, id: &str, limit: usize, offset: usize, ) -> ServiceResult<Vec<Triple>>
Page through every active triple.
Sourcepub async fn kg_count(&self, id: &str) -> ServiceResult<usize>
pub async fn kg_count(&self, id: &str) -> ServiceResult<usize>
Return the count of currently-active triples.
Sourcepub async fn kg_graph(&self, id: &str) -> ServiceResult<KgGraphPayload>
pub async fn kg_graph(&self, id: &str) -> ServiceResult<KgGraphPayload>
Build the per-palace visual graph payload.
Sourcepub async fn dream_status_aggregate(&self) -> DreamStatusPayload
pub async fn dream_status_aggregate(&self) -> DreamStatusPayload
Aggregate dream stats across every persisted palace.
Sourcepub async fn dream_status_for_palace(
&self,
id: &str,
) -> ServiceResult<DreamStatusPayload>
pub async fn dream_status_for_palace( &self, id: &str, ) -> ServiceResult<DreamStatusPayload>
Per-palace dream stats snapshot.
Sourcepub async fn dream_run(&self) -> ServiceResult<DreamStatusPayload>
pub async fn dream_run(&self) -> ServiceResult<DreamStatusPayload>
Run a dream cycle across every palace.
Sourcepub async fn list_activity(
&self,
filter: ActivityFilter,
limit: usize,
offset: usize,
) -> ServiceResult<(Vec<ActivityEntry>, u64)>
pub async fn list_activity( &self, filter: ActivityFilter, limit: usize, offset: usize, ) -> ServiceResult<(Vec<ActivityEntry>, u64)>
Paginated activity-log read.
Sourcepub fn open_handle(&self, id: &str) -> ServiceResult<Arc<PalaceHandle>>
pub fn open_handle(&self, id: &str) -> ServiceResult<Arc<PalaceHandle>>
Open the named palace, returning ServiceError::NotFound on failure.
Trait Implementations§
Source§impl Clone for MemoryService
impl Clone for MemoryService
Source§fn clone(&self) -> MemoryService
fn clone(&self) -> MemoryService
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreAuto Trait Implementations§
impl Freeze for MemoryService
impl !RefUnwindSafe for MemoryService
impl Send for MemoryService
impl Sync for MemoryService
impl Unpin for MemoryService
impl UnsafeUnpin for MemoryService
impl !UnwindSafe for MemoryService
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more