Skip to main content

AppState

Struct AppState 

Source
pub struct AppState {
    pub version: String,
    pub registry: Arc<PalaceRegistry>,
    pub data_root: PathBuf,
    pub embedder: Arc<OnceCell<Arc<FastEmbedder>>>,
    pub default_palace: Option<String>,
    pub chat_provider: Arc<OnceCell<Option<Arc<dyn ChatProvider>>>>,
    pub session_stores: Arc<DashMap<String, Arc<ChatSessionStore>>>,
    pub events: Arc<Sender<DaemonEvent>>,
    pub started_at: Instant,
    pub log_buffer: LogBuffer,
    pub disk_bytes: Arc<AtomicU64>,
    pub sys_metrics: Arc<Mutex<SysMetrics>>,
}
Expand description

Shared application state passed to every request handler.

Why: The stdio loop and HTTP server need the same handles to the registry, data root, and embedder so MCP tools can perform real reads/writes against the live trusty-memory core. The embedder is heavy (loads ONNX weights) so we hold it behind a OnceCell and initialize lazily on first use. What: Clone-able via Arc fields. The registry / data root are eager; embedder is Arc<OnceCell<Arc<FastEmbedder>>> so concurrent first-use races resolve to a single shared instance. Test: app_state_default_constructs confirms construction without panic.

Fields§

§version: String§registry: Arc<PalaceRegistry>§data_root: PathBuf§embedder: Arc<OnceCell<Arc<FastEmbedder>>>§default_palace: Option<String>

Optional default palace applied to MCP tool calls when the caller omits the palace argument. Set via trusty-memory serve --palace.

§chat_provider: Arc<OnceCell<Option<Arc<dyn ChatProvider>>>>

Active chat provider selected at startup. None means no upstream is configured (no Ollama detected and no OpenRouter key) — callers must degrade gracefully (chat endpoint returns 412).

§session_stores: Arc<DashMap<String, Arc<ChatSessionStore>>>

Per-palace chat-session stores, opened lazily so cold-start cost is paid only when chat-history endpoints are hit.

§events: Arc<Sender<DaemonEvent>>

Broadcast sender for live DaemonEvent pushes to SSE subscribers.

Why: Lets mutating handlers emit events that any connected dashboard receives instantly. Cap of 128 buffers transient slow readers; if a receiver lags it gets RecvError::Lagged and we emit a lag frame.

§started_at: Instant

Instant the daemon started, used to compute uptime_secs on /health.

Why (issue #35): GET /health reports how long the daemon has been up. Capturing a monotonic Instant at AppState construction lets the handler compute the elapsed seconds cheaply and without a clock-skew hazard. What: a wall-monotonic Instant; AppState::new stamps it at startup. Test: health_endpoint_includes_resource_fields.

§log_buffer: LogBuffer

In-memory ring buffer of recent tracing log lines (issue #35).

Why: the GET /api/v1/logs/tail endpoint serves the last N log lines so operators can inspect a running daemon without tailing a file. The buffer is shared between the tracing LogBufferLayer (writer) and the HTTP handler (reader). What: a cheap Arc-backed clone of the buffer the subscriber writes to. Defaults to an empty buffer for states that never install the layer (tests, the stdio path). Test: logs_tail_returns_recent_lines.

§disk_bytes: Arc<AtomicU64>

Most recent on-disk footprint of data_root, in bytes (issue #35).

Why: GET /health reports disk_bytes. Walking the data directory on every health request would make a frequent health poll do unbounded I/O; a background task recomputes it every 10 s and stores it here so the handler reads it lock-free. What: an AtomicU64 updated by the ticker spawned in run_http_on. 0 until the first walk completes. Test: health_endpoint_includes_resource_fields.

§sys_metrics: Arc<Mutex<SysMetrics>>

Per-process RSS + CPU sampler, refreshed on each /health request (issue #35).

Why: CPU usage is a delta between two sysinfo refreshes, so the sampler must persist between requests — hence the shared Mutex. What: a tokio::sync::Mutex<SysMetrics> so the async health handler can sample without blocking the runtime. Test: health_endpoint_includes_resource_fields.

Implementations§

Source§

impl AppState

Source

pub fn new(data_root: PathBuf) -> Self

Construct an AppState rooted at the given on-disk data directory.

Why: The CLI (serve) and integration tests need to point the MCP server at different roots — production at dirs::data_dir, tests at a tempfile::tempdir(). What: Builds an empty PalaceRegistry, captures the version, and allocates an empty OnceCell for the embedder. default_palace is None; use with_default_palace to set it. Test: tools::tests::dispatch_palace_create_persists constructs an AppState pointed at a tempdir and round-trips a palace through it.

Source

pub async fn load_palaces_from_disk(&self) -> Result<usize>

Scan data_root/palaces/ and re-register every persisted palace into the in-memory PalaceRegistry.

Why: AppState::new builds an empty registry, so after a daemon restart palace_list / the dashboard reported zero palaces even though dozens existed on disk — palace metadata was persisted by palace_create but never re-hydrated on startup. This method closes that gap by walking the on-disk layout (each subdirectory holding a palace.json is one palace) and rebuilding a live PalaceHandle for each, so recall paths see the full set immediately after a restart. What: Runs the blocking filesystem walk + per-palace PalaceHandle::open on a spawn_blocking thread (so it never stalls the async runtime), registers each successfully opened palace via register_arc, logs every load at debug!, and returns the count loaded. A palace that fails to open (corrupt index, unreadable kg.db, etc.) is logged at warn! and skipped — one bad palace must not abort startup or crash the daemon. Test: tests::load_palaces_from_disk_rehydrates_registry writes two palaces into a tempdir, constructs an AppState, calls this method, and asserts the returned count and registry contents.

Source

pub fn with_log_buffer(self, buffer: LogBuffer) -> Self

Builder-style: attach the daemon’s shared [LogBuffer] so the GET /api/v1/logs/tail endpoint serves the same lines the tracing subscriber captures (issue #35).

Why: main builds the buffer (via init_tracing_with_buffer) before constructing the AppState, then hands a clone here so the HTTP handler and the tracing layer observe the same ring. What: replaces the empty default buffer with the supplied one. Test: logs_tail_returns_recent_lines.

Source

pub fn emit(&self, event: DaemonEvent)

Send a DaemonEvent to all connected SSE subscribers.

Why: Mutating handlers call this after a successful write so the dashboard can update without polling. The send is best-effort — broadcast::Sender::send returns Err only when there are no live receivers, which is fine (no listeners == no work to do). What: Drops the result, so callers don’t need to care whether anyone is listening. Test: web::tests::sse_stream_receives_palace_created confirms a subscriber observes the emitted event.

Source

pub fn session_store(&self, palace_id: &str) -> Result<Arc<ChatSessionStore>>

Open (or return cached) the chat-session store for a palace.

Why: Chat session persistence lives in a dedicated SQLite file under the palace’s data dir (chat_sessions.db) so it doesn’t intermingle with the KG’s transactional load. The store is cheap to clone via Arc but the underlying r2d2 pool should be reused, so cache by id. What: Creates the palace data dir if missing, opens (or reuses) a ChatSessionStore and stashes an Arc in the DashMap. Test: Indirectly via the session HTTP handlers in web::tests.

Source

pub fn with_default_palace(self, name: Option<String>) -> Self

Builder-style setter for the default palace name.

Why: serve --palace <name> wants to bind every tool call to a project-scoped namespace without forcing every MCP request to repeat the palace argument. What: Returns self with default_palace = Some(name). Test: default_palace_used_when_arg_omitted covers the resolution path; this setter is exercised there.

Source

pub async fn chat_provider(&self) -> Option<Arc<dyn ChatProvider>>

Resolve (or initialize) the shared embedder.

Why: FastEmbedder load is expensive — we share one instance across all tool calls; the OnceCell ensures concurrent first-use races collapse to a single load. What: Returns Arc<FastEmbedder> on success. Errors propagate from the underlying ONNX load. Test: Indirectly via dispatch_remember_then_recall. Resolve the active chat provider, auto-detecting on first call.

Why: Provider selection depends on filesystem-loaded config plus a network probe (Ollama liveness), so it must be lazily initialised at runtime. Caching the choice in a OnceCell keeps it stable across concurrent requests without re-probing on every chat call. What: On first use loads ~/.trusty-memory/config.toml, prefers an auto-detected Ollama instance (when local_model.enabled), and falls back to OpenRouter when an API key is set. Returns Ok(None) when neither is available so the caller can emit a 412. Test: web::tests::providers_endpoint_returns_payload covers the detection path indirectly through /api/v1/chat/providers.

Source

pub async fn embedder(&self) -> Result<Arc<FastEmbedder>>

Trait Implementations§

Source§

impl Clone for AppState

Source§

fn clone(&self) -> AppState

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

impl Debug for AppState

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. 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