Skip to main content

MemoryService

Struct MemoryService 

Source
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

Source

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.

Source

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.

Source

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.

Source

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).

Source

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).

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub async fn kg_query( &self, id: &str, subject: &str, ) -> ServiceResult<Vec<Triple>>

Query the KG for all active triples whose subject matches.

Source

pub async fn kg_assert(&self, id: &str, body: KgAssertBody) -> ServiceResult<()>

Assert a triple in the KG.

Source

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.

Source

pub async fn kg_list_subjects( &self, id: &str, limit: usize, ) -> ServiceResult<Vec<String>>

List distinct subjects in the KG.

Source

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.

Source

pub async fn kg_list_all( &self, id: &str, limit: usize, offset: usize, ) -> ServiceResult<Vec<Triple>>

Page through every active triple.

Source

pub async fn kg_count(&self, id: &str) -> ServiceResult<usize>

Return the count of currently-active triples.

Source

pub async fn kg_graph(&self, id: &str) -> ServiceResult<KgGraphPayload>

Build the per-palace visual graph payload.

Source

pub async fn dream_status_aggregate(&self) -> DreamStatusPayload

Aggregate dream stats across every persisted palace.

Source

pub async fn dream_status_for_palace( &self, id: &str, ) -> ServiceResult<DreamStatusPayload>

Per-palace dream stats snapshot.

Source

pub async fn dream_run(&self) -> ServiceResult<DreamStatusPayload>

Run a dream cycle across every palace.

Source

pub async fn list_activity( &self, filter: ActivityFilter, limit: usize, offset: usize, ) -> ServiceResult<(Vec<ActivityEntry>, u64)>

Paginated activity-log read.

Source

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

Source§

fn clone(&self) -> MemoryService

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more